Logo: TechTrax...brought to you by MouseTrax Computing Solutions

C++ - Variables and Organization of Files

by Matt Billock

Welcome back to my little corner of Techtrax. Last month we covered your basic program, and writing text to the screen. This month, we’ll learn to start doing things that are a bit more useful. Sure, writing a program that can write a line or three of text to the screen is cool and all, but what if we want to do something useful? There are only so many programs that pull their weight by writing text to the screen, right? So let’s dig in!

Functions

Last month, we mentioned in passing a feature of C++ (and most programming languages, to be honest) that is very useful to us. It centers on the writing of functions, which are small, named snippets of code that can be called upon repeatedly. Let’s take the following example program:

#include <iostream>
using namespace std;

void Steve(void);

void main(int argc, char* argv)
{
  Steve();
}

void Steve(void)
{
   cout<<"Hi, I'm Steve!"<<endl;
}
Code Sample 1 – Fun with Functions

This is pretty much the same thing we wrote last month, with the obvious addition of several lines. First, the line:

using namespace std;

This is shorthand to reduce some ambiguity in the code. C++ has this feature called “namespaces,” which help programmers to organize their code. We won’t cover using this just yet. The main things you need to know at this point is that placing this line in the file allows us to simplify our code a bit. It wasn’t truly necessary last month (as I did that all in notepad and with the command line), but since I’ve bumped myself up to an IDE, we need to step into the modern age. What this is useful for is the “cout” and “endl” code fragments. Without this one line of code, we would have to use “std::cout” and “std::endl” instead. Just a little bit of work up front to make things easier for us.

The next line of interest:

void Steve(void);

is what is called a function prototype. This basically tells the compiler that later in the file, it is going to encounter a function called “Steve” that takes no parameters “(void)” and returns nothing. Any code after this point can now access this function at will. This is a very crucial point to keep in mind – if the code you are trying to reference doesn’t appear in at least some form before you actually reference it, you’re going to get errors. 

It is worth noting that this step is also somewhat optional – instead of just having a prototype, you could mitigate the problem by just putting the entire function before the point at which it is used. However, as you can imagine, this can quickly become very frustrating, as you now have to make absolutely sure that you keep functions in the proper order as you write them. So, in short, it’s better in the long run to just write function prototypes than to try and spend hours properly formatting your code.

Next, we’ve replaced the “Hello, World!” statement from the original program with the following:

Steve();

This, very simply, tells the compiler that at this point in the program we want to run the function “Steve”. The name is, of course, the function we wish to call, and the parenthesis afterwards denote our parameter list, which is empty for the moment (we’ll touch more on this in a bit).

Finally, the function itself:

void Steve(void)
{
  cout<<"Hi, I'm Steve!"<<endl;
}

As you can see, the first line is actually the function’s prototype, minus the semicolon. This maps the code that follows between the braces ({}) to the prototype that we specified earlier. It’s kind of like saying to the compiler “Hey, you know that guy Steve I told you about? He lives here.” Given that starting point, the compiler knows to execute all code between the braces every time you make a reference to Steve() (kind of like the programmer talking to the compiler, saying “Hey, I’m bored, let’s go talk to Steve!” And I swear, that’s the last time I’ll talk about a program like that. I think).

So, basically, when we put it all together, all we’ve done is moved the “Hello, World!” statement out of Main, given it a name, and then told the Main statement where to look for the missing code. Predictably, this code outputs to the console:

“Hi, I’m Steve!”

And that’s all there is to functions. Most everything else comes from fiddling with data, which we’ll cover next.

Variables and Data Types

Now, as we mentioned earlier, what’s so great about a program that can write text to a screen? Sure, it’s a vast improvement over the computers of yore that occupied two rooms and struggled to light a bulb at the proper time, but how does it really help me? Well, to answer that question, it’s time to introduce a new concept, known as variables. Variables are, in essence, small bits of memory that hold a certain type of information. That information can be anything, from the number 42 to the first few pages of “The Hitchhiker’s Guide to the Galaxy”. All that we need to do is tell the compiler how much space we need, and what we want to put there.

Let’s address the first task: telling the compiler how much space we need. In C++, there are several basic data types, which signify to the compiler a number of things when they are declared. First, they tell the compiler how much space the data is going to need. Then, they tell the compiler how to read the data that ends up in that space.

The second task is a bit more abstract. In order to make things simpler for the little guy (and indeed to keep C++ classified as a fourth-generation language), we’ve got a useful abstraction that saves us all the trouble of assigning bit values to registers on the processor, and takes several pages of assembly code out of our program. This abstraction is known as a variable declaration, and it goes a little something like this:

int i;

Simple, right? This little segment of code tells the compiler to allocate enough memory for an int variable (int is short for integer, or non-decimal number), and that that segment of memory will from here on out be known as “i” (Now, this isn’t entirely true – it doesn’t actually access the memory location directly. To do that we’d need a pointer, which is a batch of fun to be covered in a later tutorial – but the example serves our purposes for the moment). Now, every time that I use the letter “i” in the program, the compiler is going to go over to the area of memory that has the value for “i” and bring it back to the program. Pretty neat, huh?

Well, not really. You see, we haven’t actually done anything yet. The most useful bit comes here:

i = 0;

With this one monumental step, we tell the compiler “Hey, make ‘i’ be 0.” And the compiler says “Thy will be done,” and we all dance joyously around the fire, basking in the glory of our own greatness, as from here on out “i” is known as 0. Or something. The practical upshot of all this is that from here on out, whenever “i” is referenced, the compiler will know that you really mean “0”. This works over and over again – as many times as we care to assign a new value to “i”. For example, if we were to suddenly write

i=2;

the compiler would know that from that point on I should be known as 2, overwriting the 0.

The great part of this is that we get all sorts of data types to play with. Here’s a short list of the more common data types

  • int – an integer, otherwise known as a non-decimal number

  • char – a single character

  • float – a decimal number

  • bool – a Boolean (true/false) value

  • long – a big number

  • double – a big decimal

A more complete repository of data types can be found here

What’s more, we can actually manipulate this data. There is a whole series of operators available in our repertoire. Right now, we’re only going to concentrate on the 4 basic operators (which, coincidentally, only really apply to numbers):

  • + = addition

  • - = subtraction

  • * = multiplication

  • / = division

These are predictably used in our program. For example, the following code:

int i;
i = 0;
i = i+3;
cout<<i<<endl;

will, predictably, output “3” to the screen. To see why this happens, just take a look at the code. The first thing we do is define our variable i to be of type integer. Next, we initialize the variable (that is, store a default value into the object) to a standard value (0). Then, we take i and add 3 to it, and store the result back into i. Thus, when the program accesses i to output it to the console, it reads in the new value 3.

So that’s great, right? We’re one step closer to useful code. But there’s just one small problem we need to take care of. To see this problem, let’s take a look at the following code:

int i;
i = 0;
i = 1/2;
cout<<i<<endl;

Intuition tells us that this code should output 0.5 to the screen. However, it actually outputs 0! The reason it does this is a little obscure, and can be just the slightest bit frustrating. The issue lies behind the type of variable we are trying to store the information in. When we run the code, and try to store the value 0.5 to the integer I, the compiler gets confused for just a moment. Expecting an integer, it doesn’t quite know what to do with the decimal (known in programming as floating-point value) it has received. So it makes an assumption based on what the programmer seems to have intended. It simply drops the decimal, and stores the value as an integer. And when I say “drops the value” I mean like it never existed. That fraction is gone. This applies any time you try to store a floating-point value to an integer type. If you tried to store 5.3245, you’d get 5 as a result. If you tried to store 8.99999999999995, you’d get 8. If you tried to store 3.14 to an integer, well, Pi would be exactly 3 (I’m sorry it had to come to that).

The fix should, of course, be obvious – just change i to be a floating-point value, as in the following code:

float i;
i = 0;
i = 1/2;
cout<<i<<endl;

Now, confident in the perfection of our program, we run the code and… it still puts out 0. Now I know what you’re thinking (though your thoughts may not have as many expletives as mine), and I know – it’s frustrating. There’s a fairly simple explanation as to why this happens. Basically, when you have a concrete value in your code (known as a literal), the compiler has to store it somewhere. So, in its quest for the proper type, it simply looks for the smallest data type that can accurately represent the input and output values. In this case, as there are no decimal points anywhere, it treats both values as integers, and thus the dreaded 0 is returned. 

However, this time we’ve got a solution too. If we change the code like so:

float i;
i = 0;
i = 1/2.0;
cout<<i<<endl;

the console reads “0.5”. Finally! Coincidentally, there is another solution to this particular conundrum, but since it touches upon a more advanced topic, we’ll leave it to a later tutorial. All you should be thinking about at this point is making sure that you are working with the correct types of data.

File Organization

Next, we’re going to briefly touch upon a rather useful part of C++ programming. Let’s say you’ve got a program that has grown rather large, and has over 200 functions. Now, we could write this all in a single file, but imagine if someone had to come along and find a bug somewhere in the program. In the best case, the bug would be right at the top of the file. But let’s say the function lies in function number 198 – that’s a lot of searching. C++ provides us with some useful functionality to help ease things on the programmer. They’re known as include files and include directives.

We’ll cover the second part first, since we’ve actually already seen it. Remember the statement

#include <iostream>

As I mentioned last month, this will go into the compiler’s include directory and look for a file entitled “iostream.h” (note, this is a correction to last month’s article – my bad. There probably isn’t an include.h. I meant iostream – sorry. I will publicly flog myself in repentance). This file contains a whole series of useful functions for us to, well, make use of. The cool thing about this statement is that we can use it to look for pretty much any file we write with a fairly simple change:

#include “myInclude.h”

This tells the compiler to start in the program’s directory (the one with our main.cpp) and look for the file “myInclude.h”. If it can’t find it in that folder, it then moves on to the compiler’s include directory (buried somewhere in c:\Program Files). Anything it finds in this file it treats as if it was a part of the same file. This proves very useful for us, as we’ll see in a moment.

But before we show how useful this is, we have to cover some conventions. If you’re a careful observer, you’ll notice that I’ve used two different extensions when referring to code files – namely, something.cpp and something.h. These extensions have important definitions:
<ul>
<li>.cpp indicates a code file, or a file that contains the code for various prototypes (known as method implementations)</li>
<li> .h indicates a header file. A header file, by convention, contains function prototypes like those above (among other things, which we shall see in coming months), which will be implemented in a code file.</li>

<ul>
The compiler implements this functionality in a rather strange way. Let’s take, for example, the following set of files:

File – main.cpp
#include <iostream>
#include "headerTest.h"
using namespace std;

void main(int argc, char* argv)
{
  Steve();
}

File – headerTest.h
#include <iostream>
using namespace std;
void Steve(void);

File – headerTest.cpp
#include "headerTest.h"

void Steve(void)
{
  cout<<"Hi, I'm Steve!"<<endl;
}
Code Sample 2 – Files galore

This set of files (3 in total – main.cpp, headerTest.h, and headerTest.cpp) are simply a different way of writing our program from code sample 1. We’ve taken the prototype for the function Steve(), and given it its own file – headerTest.h. Then, we take the implementation for that method and put it in yet another file – headerTest.cpp – which includes “headerTest.h” as its first order of business. Then we simply include “headerTest.h” in our main.cpp, and all of a sudden it’s like Steve() never left. This will work for pretty much any file we write – there are just a few things to keep in mind when using this functionality:

  • The include directive (the statement preceded by the # sign) must be one of the first lines in the code (outside of any comments describing the file). This is really more a convention issue than anything else, although it does have some use if you happen to include two files that declare the same function – simply reorganizing the include files can solve this issue, and it’s a lot easier to do when the include directives are right next to each other

  • If you wrote the file, you have to reference it with quotes (“”). If you don’t, the compiler will go looking in its own include directory for your code file (which is, of course, in your program’s directory), and will throw “unresolved external symbol” errors when it can’t find your file

  • Notice how the headerTest.h file makes no reference to the .cpp file. Visual Studio knows to look for a .cpp file whenever it stumbles across a .h file – it’s just smart like that. This is not true of all compilers, however – specifically, some command-line compilers may not enjoy this. Figuring out the proper parameters for the command line will be a matter of searching through your compiler’s documentation, if the issue arises

And with that, I’m pretty much done for the month. Thanks for reading, and tune in next month for our next exciting episode. And don’t forget – if you have any trouble, or any questions, drop me an e-mail at evilstickman@evilstickman.com.

Click to rate this article.

 

Go up to the top of this page.

This site powered by the Logical Web Publisher™: Content management by Logical Expressions, Inc.