Saturday, December 11, 2010

Arrays as parameters

At some moment we may need to pass an array to a function as a parameter. In C++ it is not possible to pass a complete block of memory by value as a parameter to a function, but we are allowed to pass its address. In practice this has almost the same effect and it is a much faster and more efficient operation.

In order to accept arrays as parameters the only thing that we have to do when declaring the function is to specify in its parameters the element type of the array, an identifier and a pair of void brackets []. For example, the following function: 


 
void procedure (int arg[])


accepts a parameter of type "array of int" called arg. In order to pass to this function an array declared as:

 
int myarray [40];


it would be enough to write a call like this:

 
procedure (myarray);


Here you have a complete example: 


// arrays as parameters
#include <iostream>
using namespace std;

void printarray (int arg[], int length) {
  for (int n=0; n<length; n++)
    cout << arg[n] << " ";
  cout << "\n";
}

int main ()
{
  int firstarray[] = {5, 10, 15};
  int secondarray[] = {2, 4, 6, 8, 10};
  printarray (firstarray,3);
  printarray (secondarray,5);
  return 0;
}
5 10 15
2 4 6 8 10


As you can see, the first parameter (int arg[]) accepts any array whose elements are of type int, whatever its length. For that reason we have included a second parameter that tells the function the length of each array that we pass to it as its first parameter. This allows the for loop that prints out the array to know the range to iterate in the passed array without going out of range.

In a function declaration it is also possible to include multidimensional arrays. The format for a tridimensional array parameter is:

 
base_type[][depth][depth]


for example, a function with a multidimensional array as argument could be: 

 
void procedure (int myarray[][3][4])


Notice that the first brackets [] are left blank while the following ones are not. This is so because the compiler must be able to determine within the function which is the depth of each additional dimension.

Arrays, both simple or multidimensional, passed as function parameters are a quite common source of errors for novice programmers. I recommend the reading of the chapter about Pointers for a better understanding on how arrays operate.

Multidimensional arrays

Multidimensional arrays can be described as "arrays of arrays". For example, a bidimensional array can be imagined as a bidimensional table made of elements, all of them of a same uniform data type.



jimmy represents a bidimensional array of 3 per 5 elements of type int. The way to declare this array in C++ would be:


 
int jimmy [3][5];


and, for example, the way to reference the second element vertically and fourth horizontally in an expression would be: 

 
jimmy[1][3]




(remember that array indices always begin by zero).

Multidimensional arrays are not limited to two indices (i.e., two dimensions). They can contain as many indices as needed. But be careful! The amount of memory needed for an array rapidly increases with each dimension. For example:

 
char century [100][365][24][60][60];


declares an array with a char element for each second in a century, that is more than 3 billion chars. So this declaration would consume more than 3 gigabytes of memory!

Multidimensional arrays are just an abstraction for programmers, since we can obtain the same results with a simple array just by putting a factor between its indices:

1
2
int jimmy [3][5];   // is equivalent to
int jimmy [15];     // (3 * 5 = 15) 


With the only difference that with multidimensional arrays the compiler remembers the depth of each imaginary dimension for us. Take as example these two pieces of code, with both exactly the same result. One uses a bidimensional array and the other one uses a simple array: 

multidimensional arraypseudo-multidimensional array
#define WIDTH 5
#define HEIGHT 3

int jimmy [HEIGHT][WIDTH];
int n,m;

int main ()
{
  for (n=0;n<HEIGHT;n++)
    for (m=0;m<WIDTH;m++)
    {
      jimmy[n][m]=(n+1)*(m+1);
    }
  return 0;
}
#define WIDTH 5
#define HEIGHT 3

int jimmy [HEIGHT * WIDTH];
int n,m;

int main ()
{
  for (n=0;n<HEIGHT;n++)
    for (m=0;m<WIDTH;m++)
    {
      jimmy[n*WIDTH+m]=(n+1)*(m+1);
    }
  return 0;
}


None of the two source codes above produce any output on the screen, but both assign values to the memory block called jimmy in the following way: 



We have used "defined constants" (#define) to simplify possible future modifications of the program. For example, in case that we decided to enlarge the array to a height of 4 instead of 3 it could be done simply by changing the line:

 
#define HEIGHT 3 

to:
 
#define HEIGHT 4 


with no need to make any other modifications to the program. 

Accessing the values of an array.

In any point of a program in which an array is visible, we can access the value of any of its elements individually as if it was a normal variable, thus being able to both read and modify its value. The format is as simple as:


name[index]


Following the previous examples in which billy had 5 elements and each of those elements was of type int, the name which we can use to refer to each element is the following:



For example, to store the value 75 in the third element of billy, we could write the following statement:


 
billy[2] = 75;


and, for example, to pass the value of the third element of billy to a variable called a, we could write:

 
a = billy[2];


Therefore, the expression billy[2] is for all purposes like a variable of type int.

Notice that the third element of billy is specified billy[2], since the first one is billy[0], the second one is billy[1], and therefore, the third one is billy[2]. By this same reason, its last element is billy[4]. Therefore, if we write billy[5], we would be accessing the sixth element of billy and therefore exceeding the size of the array.

In C++ it is syntactically correct to exceed the valid range of indices for an array. This can create problems, since accessing out-of-range elements do not cause compilation errors but can cause runtime errors. The reason why this is allowed will be seen further ahead when we begin to use pointers.

At this point it is important to be able to clearly distinguish between the two uses that brackets [ ] have related to arrays. They perform two different tasks: one is to specify the size of arrays when they are declared; and the second one is to specify indices for concrete array elements. Do not confuse these two possible uses of brackets [ ] with arrays.

1
2
int billy[5];         // declaration of a new array
billy[2] = 75;        // access to an element of the array. 


If you read carefully, you will see that a type specifier always precedes a variable or array declaration, while it never precedes an access.

Some other valid operations with arrays:

1
2
3
4
billy[0] = a;
billy[a] = 75;
b = billy [a+2];
billy[billy[a]] = billy[2] + 5;




// arrays example
#include <iostream>
using namespace std;

int billy [] = {16, 2, 77, 40, 12071};
int n, result=0;

int main ()
{
  for ( n=0 ; n<5 ; n++ )
  {
    result += billy[n];
  }
  cout << result;
  return 0;
}
12206

Initializing arrays.

When declaring a regular array of local scope (within a function, for example), if we do not specify otherwise, its elements will not be initialized to any value by default, so their content will be undetermined until we store some value in them. The elements of global and static arrays, on the other hand, are automatically initialized with their default values, which for all fundamental types this means they are filled with zeros.

In both cases, local and global, when we declare an array, we have the possibility to assign initial values to each one of its elements by enclosing the values in braces { }. For example:


 
int billy [5] = { 16, 2, 77, 40, 12071 }; 


This declaration would have created an array like this:



The amount of values between braces { } must not be larger than the number of elements that we declare for the array between square brackets [ ]. For example, in the example of array billy we have declared that it has 5 elements and in the list of initial values within braces { } we have specified 5 values, one for each element.

When an initialization of values is provided for an array, C++ allows the possibility of leaving the square brackets empty [ ]. In this case, the compiler will assume a size for the array that matches the number of values included between braces { }:

 
int billy [] = { 16, 2, 77, 40, 12071 };


After this declaration, array billy would be 5 ints long, since we have provided 5 initialization values.

Arrays

An array is a series of elements of the same type placed in contiguous memory locations that can be individually referenced by adding an index to a unique identifier.

That means that, for example, we can store 5 values of type int in an array without having to declare 5 different variables, each one with a different identifier. Instead of that, using an array we can store 5 different values of the same type, int for example, with a unique identifier.

For example, an array to contain 5 integer values of type int called billy could be represented like this:



where each blank panel represents an element of the array, that in this case are integer values of type int. These elements are numbered from 0 to 4 since in arrays the first index is always 0, independently of its length.

Like a regular variable, an array must be declared before it is used. A typical declaration for an array in C++ is:


type name [elements];


where type is a valid type (like int, float...), name is a valid identifier and the elements field (which is always enclosed in square brackets []), specifies how many of these elements the array has to contain.

Therefore, in order to declare an array called billy as the one shown in the above diagram it is as simple as:


 
int billy [5];


NOTE: The elements field within brackets [] which represents the number of elements the array is going to hold, must be a constant value, since arrays are blocks of non-dynamic memory whose size must be determined before execution. In order to create arrays with a variable length dynamic memory is needed, which is explained later in these tutorials.

Writing In a File

#include<iostream>
#include<fstream> // for file handling
unsing namespace std;
int main()
{
     ofstream out; // declaring a variable
     out.open("file.txt"); // opening the file 
 
  
     out.close(); // closes the file
     return 0;
}

Reading From a File

#include<iostream>
#include<fstream> // for file handling
unsing namespace std;
int main()
{
     ifstream in; // declaring a variable
     in.open("file.txt"); // opening the file 
     if(!in)
            {
                   cout<<"No File is Found"<<endl; // if file was not present
                   return 0;
             }
     while(in) // this while loop runs till the file is not end
      ............... // this portion includes the work you want to do with the file
     in.close(); // closes the file
     return 0;
}

What is File handling?

 Many real-life problems handle large volumes of data, therefore we need to use some devices such as floppy disk or hard disk to store the data.

The data is stored in these devices using the concept of files. A file is a collection of related data stored in a particular area on the disk.

Programs can be designed to perform the read and write operations on these files.

A program typically involves either or both of the following kinds of data communication:
Data transfer between the console unit and the program.
Data transfer between the program and a disk file.
The input/output system of C++ handles file operations which are very much similar to the console input and output operations.

It uses file streams as an interface between the programs and the files.

The stream that supplies data to the program is known as input stream and the one that receives data from the program is known as output stream.

In other words, the input stream extracts or reads data from the file and the output stream inserts or writes data to the file.

Object Design

There are entire books and long college courses that discuss Object design, I will go through the basics. As discussed in the previous section, An object (synonymous with a class in C++) has data that it remembers and actions that it can perform. So once it has been determined how an object fits into a program, it's member data and member functions can be determined. The question becomes how to determine what role within a program an object is will play.
The role that an object plays within a program should be able to be defined in one to three short sentences. If it takes more than this to define (generally) an object's role, then there should be more than one object. For instance, good concise role's for an object would be:

  • Manages all requests into a data structure
    OR
  • Arbitrates turns in a multi-user game
    OR
  • does all data type conversions within program
    OR
  • easy interface to reading, writing, and parsing files.
Drawing pictures that describe the functionality of objects can be a big help when designing a program. Pictures can often describe the relationship between objects better than a paragraph of words. Objects relate to each other in the following ways: ownership (contains), contained-by, knows-about, doesn't-know-about.
Here's a picture of how some of the objects in the text-based medieval video game relate to each other:
Figure 1
Players and Monsters need to be able to fight each other. Otherwise it wouldn't be a very exciting game. When the Player and a Monster are Battling they battle through a third object, the BattleMgr. The BattleMgr decides which Battler acts first, and eventually, which battler wins. So the Player object and the monster object need to know about the BattleMgr, and the BattleMgr needs to know about both the Player and the Monster.
We have already decided on the attributes of a Battler, so let's add these to the picture.
Figure 2
Remember our pseudo-code for a battler:
Player Object:
data:
    health
    strength
    agility
    type of weapon
    type of armor
actions:    
    move    
    attack monster    
    get treasure
END
Notice that the actions are not listed in the picture, Just the attributes (data). Weapons and Armor are their own type of objects so players/monsters must know about one of each of these types of objects. The reason that health, strength, and agility are treated differently will become clear later. For now, understand what we are trying to accomplish by drawing a picture: we want to first of all understand the relationships between objects. Secondly we want to talk our way through the program with this picture and make sure that we aren't leaving something out.Let's move to a more complex example. In our game, a player can move through a maze encountering Monsters to battle, and weapons and treasure to pick up.
We still have Players, Monsters, and a BattleMgr. We also have a GameDatabase (GameDB) which keeps track of all the rooms in the maze and what is in them. We also have a RoomManager (RoomMgr) that keeps track of the current room where the player is and interfaces with the GameDB when the player moves to see where the player ends up.
Here is the diagram:
Figure 3
So let's go through some parts of the game. The player starts the game and decides to go east: so the Player object tells the RoomMgr that the player is going east. The RoomMgr checks that the move is valid and then asks the GameDB for the next room.
We already have a few problems. The RoomMgr needs an instance variable to keep track of the current room. How does the RoomMgr know if "east" is a valid move? Well, each room will have to have 4 instance variables: east, west, north, and south. Their values will be 1 if that is a valid direction and 0 if it is not a valid direction. What about the contents of the room. What can rooms contain? We'll have to create a Treasure object and rooms will have to keep a list of what Treasure they contain and also what Monsters reside in that room.
How will we keep a list of an arbitrary number of items? We will use what is called a linked list. This will be a good example of reusing pieces of code.
So where do we go from here? In the next section, we will try and pseudo-code out our program. We will determine how we want everything to work and then write it down in pseduo-code.
Before going into the next section take a shot at re-drawing the previous picture and figure out how you would organize the objects and what instance variables and member functions they would contain. Your picture will no doubt be different from ours. Not that that means you are right or wrong. One of the interesting things about programming is that there are often many different ways to solve the same problem.