|

An array is a collection of data storage locations, each of which hold the same type of data.
As an analogy, let's travel
back to the golden era of the
glorious seventies :) and revel in one of the miracles of modern
plastics. A time of
wonderful, colorful music and cool,
colorful polyester and shirts with giant collars. The
age of muscle cars and gas
rationing. Lava lamps and disco
techs. Oh blissful childhood that I had, how my soul
has been stirred with affection for those days! Or is it
nausea?
"With
each passing year, because of advances in computer
technology, there are more things,
each more sophisticated, that we aren't allowed to
do any more."
- Paul Lutus |
My mother used to host
Tupperware™
parties, and had purchased a considerable amount of Tupperware
items. So let's use them as an example. If a
standard variable could be represented as a
Tupperware™
container, for which we can use to store objects of the right
type, then an array would be a series of those containers
capable of storing the same type of object. Tupperware
containers were well-built for storing objects of a certain type
- it was nothing short of miraculous the way they could keep a
sandwich fresh for lunch! Some could store single
sandwiches or servings of casserole, but the really cool
Tupperware™
containers were
those that could store an entire meal - a series of objects.
These colorful, catchy containers consisted of a series of
smaller compartments which could hold all of the different items
of yesterday's leftovers. It was like making your own
frozen entrees (at that time they were referred to as TV
dinners), except that it was made with real food that actually
tasted good. This was especially appealing to me at the
time, since I had an obsessive compulsion about my food not
touching other types of food. As interesting as this
historical digression into the time of
Tupperware™
past may be, let us return to the time of
Tupperware™
present and continue our discussion about arrays . . .
Arrays contain a series of objects that are indexed by a
subscript. The index-referenced storage location of each
item is known as an element of the array. Arrays
can be composed of pre-defined data types such as integers,
floats, and doubles or they can be composed of user-defined
objects such as Monsters, Mammals, Employees, etc. The
important thing to remember is that arrays must contain objects
of the SAME TYPE, not different types. A string object is
actually an array of ASCII char
variables, so using a char array is
synonymous with using string objects.
We declare an array by writing the type followed by the array name and then subscript. The subscript is the number of elements in the array surrounded by square brackets.
This subscript becomes a reference and a handle for manipulating
the object stored at its location. Example:
long
AReallyBigArray[25]; declares an array of 25 long integers.
The name of this array is AReallyBigArray. The compiler sets aside enough memory to hold all 25 elements. Since each long
int requires 4 bytes, this sets
aside 100 bytes of memory! Bam! Kick it up a notch.
Perhaps you are wondering what the hype is concerning arrays.
Perhaps groups of objects are things that you take for granted -
multiple instances of a class. But arrays warrant further
consideration, my friend. Arrays become incredibly
powerful when combined with looping, repetition, and decision
structures. They can be use to create binary and linear
searches, all kinds of recursion, and limitless useful
algorithms, subroutines and functions. They can become a
very quick method of populating memory with instances of an
object.
You access each of the array elements by referring to an offset from the array name. Array elements are counted from 0, therefore, the first array element is
arrayName[0].
AReallyBigArray[25] would be numbered from 0-24, not 1-25.
This creates a situation where a common mistake is often made by
programmers. Many times our code syntax may be fine, and
our projects will compile. But they will not perform as
expected. We encounter logic errors, rather than syntax
errors, and our programs do not behave as expected. This
results many times from an array out of bounds error, often
referred to as an "off by one" or "fencepost" error.
This is somthing to watch for. To declare an array of 5 integers and fill each with a value,
you would simply write the array name and type followed by the
subscript for its number of elements:
#include <iostream.h>
int main() {
int myArray[5];
for(int i=0; i<5; i++)
// 0-4 { cout << "Value for myArray[" << i << "]: "; cin >> myArray[i]; }
for(i = 0; i<5; i++) cout << i << ": " << myArray[i] << "\n";
return 0; } |
Output:
value for myArray[0]: 3 value for myArray[1]: 6 value for myArray[2]: 9 value for myArray[3]: 12 value for myArray[4]: 15 |
0: 3 1: 6 2: 9 3: 12 4: 15 |
In this example, the array
myArray is declared to hold 5
int variables. A loop is established that counts from 0 through 4, the proper set of offsets for a 5 element array. The user is prompted for a value and the value is saved at the correct offset into the array,
storing the value in the corresponding array element. Remember: Arrays count from 0, not 1,
so a 10-element array counts from 0-9.
When you write a value to an element in an array, the compiler stores the value based on the size of each element and
indexes it by the subscript. If you tried to write to
AReallyBigArray[50]
in the array
AReallyBigArray[25],
the compiler would ignore that there is no such element. It computes how far past the 1st element it should look
, in this case 200 bytes, and writes over whatever data is stored there. This causes
crashes and unpredictable errors - rather than syntax, we are
speaking of logic errors, the more difficult kind to debug.
Don't forget:
fence post error - error resulting from writing to 1 past the end of an array. (You need 11 posts for a 10-foot fence.)
Initializing an array - To initialize an array, after the array name place an = sign and a list of comma separated values enclosed in braces:
int IntegerArray[5] = {10, 20, 30, 40, 50 }; declares
IntegerArray to be an array of 5 integers. It assigns the value 10 to IntegerArray[0], the value 20 to IntegerArray[1] and so forth. If you OMIT the size of the array you will get one just big enough to hold the initialization. Example:
int IntegerArray[] = {10, 20, 30, 40, 50 }; behaves exactly as the previous array,
though the compiler determines the array size. If you need to know the size of an array, ask the compiler to compute it for you by:
const USHORT IntegerArrayLength =
sizeof(IntegerArray)/sizeof(IntegerArray[0]);
This sets
USHORT IntegerArrayLength to the size of the entire array, divided by the size of each individual entry in the array.
You may then display or use this value.
It is important to note that you cannot initialize more elements
than you have declared for an array: Using an example,
writing the expression:
int IntegerArray[5] = {10, 20, 30, 40, 50, 60};
, would create a compile time error since you've declared a 5 member array, yet
you have initialized 6 values. Though we can not assign
more elements than declared, we are permitted to assign less.
The following expression:
int IntegerArray[5] = {10, 20}; is acceptable. If you don't initialize an array member, its value is set to 0.
Arrays of objects - to access member data in an array of objects you must do 2 things:
1) Identify the member of the array by using the index
operator([ ])
2) Add the member operator (.) or (->), depending on whether
access is direct or indirect the particular member variable. Example:
//An array of objects #include <iostream.h>
class Monster {
public:
Monster() { itsAge = 1; itsWeight=5; }
//default constructor ~Monster() {}
//destructor
//Accessor methods
int GetAge()
const {
return itsAge; }
int GetWeight()
const {
return itsWeight; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
int itsWeight; };
//------------------------------------------------------------------------------------------------------------------
int main() {
//Create an array of
Monster objects.
Monster MotleyCrew[5];
int i;
//The following will
count up on odd numbers
for(i = 0; i < 5; i++) MotleyCrew[i].SetAge(2*i + 1);
for(i = 0; i < 5; i++) cout << "Monster #" << i+1<< ": " <<
MotleyCrew[i].GetAge() << endl;
return 0; } |
Output:
Monster #1: 1 Monster #2: 3 Monster #3: 5
Monster #4: 7 Monster #5: 9
The example above declares the Monster class. It must have a default constructor so that
Monster objects can be created in an array. The first for loop sets age of each of
the 5 Monsters in array. The algorithm will count up by
odd numbers by multiplying each loop iteration by 2 and then
adding 1. The second for loop accesses each member of the array and calls GetAge()
vis the dot operator and array subscript, since access is direct
in this case. If we were dealing with the indirection of
pointers or references, we would have to use
-> in this case. Again,
notice that each individual Monster's GetAge() method is called by accessing the
appropriate element in the array MotleyCrew[i] followed by the dot operator (.) and the
name of member function.
Multidimensional arrays - In multi dimensional
arrays, each dimension is represented as a separate subscript in the array. A
two dimensional array has two subscripts, a three dimensional array has
three subscripts, and so on. Using our chess game example,
in a class named SQUARE, to create a two dimensional 64 square array, you'd write SQUARE Board[8][8];
, which is the same as writing SQUARE Board[64]. In chess, when the game begins, the king is located in the 4th position of the 1st row. Counting from 0, that position corresponds to Board[0][3]; where the first subscript corresponds to row and the second
subscript to column. In this way we can employ an (x,y)
coordinate system.
Multidimensional arrays are great for coding database tables.
In a two dimensional array, one dimension can represent rows and
the other columns. To initialize a multidimensional array, assign the list of values to
the array elements in corresponding order, with the last array subscript changing while each of the former
subscripts hold steady. If you have
int theArray[5][3]; the
first 3 elements go into the theArray [0][x], the next 3 into
theArray[1][x] and so on in sets of 3 done 5
distinct times. You could initialize this array by writing:
int theArray[5][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};.
To make things clearer, you could group the initializations with
nested braces like:
int theArray[5][3] = { {1, 2, 3,}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}, {13, 14, 15} };
The compiler ignores the inner braces, they simply make
this easier to read. This illustrates more clearly that we
are assigning our objects to 5 sets of 3 objects each, for a
total of 15. Each value must be separated by a comma, and the entire set must be enclosed in braces and must end in a semicolon.
Listed elow is a two dimensional array. The first dimension is the set of numbers from 0 to 5. The
second dimension consists of the double of each value that was
stored in the first dimension:
#include <iostream.h>
int main() {
int SomeArray[5][2] = { {0,0}, {1,2}, {2,4}, {3,6}, {4,8}};
for(int i = 0; i<5; i++)
{
for(int j=0; j<2; j++) { cout << "SomeArray[" << i << "][" << j << "]: "; cout << SomeArray[i][j]<< endl; }
//end 2nd loop
}
//end 1st loop
return 0; } |
Output:
SomeArray[0][0]: 0 SomeArray[0][1]: 0 SomeArray[1][0]: 1 SomeArray[1][1]: 2 SomeArray[2][0]: 2 |
SomeArray[2][1]: 4 SomeArray[3][0]: 3 SomeArray[3][1]: 6 SomeArray[4][0]: 4 SomeArray[4][1]: 8 |
After declaring the
multi-dimensional array, SomeArray, we dive into a nested for loop.
The first for loop that iterates 5 times corresponds to the
first subscript of the array. The second for loop that
iterates twice accesses the second subscript of the array.
In this way, for each iteration of the first loop, the second
loop will iterate 2 times. This becomes the foundation for
accessing and manipulating the data stored in multidimensional
arrays.
Parallel Arrays - Parallel arrays consist of two or more
arrays with data or objects that have a relationship. the
elements inthe arrays are grouped together by this relationship.
Recall that a string is an array of type char, so to hold an
array of 5 strings we could create a multidimensional array of
chars. The array, People[5][20],
would therefore hold 5 strings of up to 20
char's each. If we combine these 5 strings with 5
ages, the data contained within the two arrays has a
relationship, therefore they are parallel arrays. Example:
//A Parallel Array
#include
<iostream.h>
void main() {
char
People[5][20] = { "Harry", "Susie", "Sally",
"Charles", "Drew" };
int
ages[5] = { 15, 24, 13, 34, 35 };
for(int
x = 0; x < 5; x++) {
cout << People[x] << " is " <<
ages[x] << endl;
}
} |
Output:
Harry is 15
Susie is 24
Sally is 13
Charles is 34
Drew is 35
When we declare an array, the compiler sets aside memory for all its objects, even if you never use them. This isn't a problem when you know how many objects you need, but when you don't, you must use more advanced data structures
such as pointers and references to create objects on the heap
(free store). By declaring objects on the free store and storing only a pointer to the object in an array, you can leave the limited constraint of stack memory and use free store memory which is far larger. This greatly reduces amount of stack memory used.
It also increases efficiency because you can iterate, search and
recurse indexed memory locations rather than entire objects. Example:
#include <iostream.h>
//------------------------------------------------------------------------------------------------------------------
class Monster {
public:
Monster() { itsAge = 1; itsWeight=5; } ~Monster()
{}
//Accessor
methods
int GetAge()
const {
return itsAge; }
int GetWeight()
const {
return itsWeight; }
void SetAge(int age) { itsAge = age; }
private:
int itsAge;
int itsWeight; };
//------------------------------------------------------------------------------------------------------------------
int main() {
//Create an array of
500 pointers to Monster objects
Monster *ArmyOfDarkness[500];
int x;
//Create a pointer
to a Monster object
Monster *MonsterPointer;
for(x = 0; x < 500; x++) {
//Create
a new Monster object on the heap and point to it
with MonsterPointer MonsterPointer = new
Monster; MonsterPointer->SetAge(2*x +
x);
//Copy
the object pointed to by MonsterPointer to the
corresponding pointer element of the array ArmyOfDarkness[x] = MonsterPointer; }
for(x = 0;
x < 500; x++) cout << "Monster #" <<
x+1 << ": " << ArmyOfDarkness[x]->GetAge() << endl;
return 0; } |
Output:
Monster #1: 1 Monster #2: 3
Monster #3: 5 |
Monster #4: 7
... and so on and so on . . . Monster #499: 997
Monster #500: 999 |
This illustrates an array
of 500 pointers to Monster objects. In this example, a
Monster object is declared. The array ,
ArmyofDarkness[500], is declared to hold 500 pointers to
Monster objects. In the first initial loop, 500 new
Monster objects are created on free store, and each has its age set to 2 times the
number of the loop iteration plus 1. In this way,
ages ascend by odd numbers, so that the first Monster is 1, the second 3, the 3rd 5, and so on
and so on. Then a pointer is added to the array. Because the array has been declared to hold pointers, the pointer rather than the dereferenced value
of the pointer, is added to the array. Finally, the
second for loop prints each of the values. The pointer is accessed by the index,
ArmyOfDarkness[x] and that address is used to access the GetAge() method. The array,
ArmyOfDarkness[500], and all its pointers, are stored on the stack but the 500
Monsters that are created using "new" are stored on the free store(heap).
We can also create an array declaring arrays on the free store - you can place an entire array on the free store(heap). To do this, call "new" and use the subscript operator. The result is a pointer to an area on the free store that holds the array.
In the first example, we had an array of 500 pointers to Monster
objects on the heap. In this next example, we will create
a single pointer to an array of 500 Monster objects on the heap. Example:
Monster
*MotleyCrew = new Monster[500];
This declares MotleyCrew to be a pointer to the first
element in an array of 500 Monsters. MotleyCrew points to,
that is it has the address of, MotleyCrew[0]. The advantage of this is that you can use pointer arithmetic to access each member of
MotleyCrew. Example:
Monster
*MotleyCrew = new Monster[500]; Monster
*MonsterPointer = MotleyCrew;
//MonsterPointer points to
MotleyCrew[0]
MonsterPointer ->SetAge(10);
//set
MotleyCrew[0] to 10
MonsterPointer++;
//advance
to element MotleyCrew[1]
MonsterPointer->SetAge(20);
//set
MotleyCrew[1] to 20
This declares a new array of 500
Monsters on the heap and a pointer that points to the start of the array. Using that pointer,
the first Monster's SetAge() function is called with the value 10. The pointer is then incremented to point to the next
Monster, and the second Monster's SetAge() method is then called.
Observe:
A pointer to an array versus an array of pointers - Observe the differences:
1)
Monster MonsterMashOne[500];
//this is an array of 500
Monsters on the free store.
2)
Monster
*MonsterMashTwo[500];
//this is an array of 500 pointers to
Monsters.
3)
Monster
*MonsterMashThree =
new
Monster[500];
//this is a
single pointer to an array of 500 Monsters on the
heap. |
The differences among these
three different types of arrays dramatically affect how they operate. The address of
MonsterMashThree is the address of the first item that array, which is the same case for
MonsterMashOne.
MonsterMashThree is only a variant of
MonsterMashOne, but very different from
MonsterMashTwo.
An array name is a const pointer to the first element of the array. In the declaration
Monster
MotleyCrew[50]; ,
MotleyCrew is a pointer to
&MotleyCrew[0] . It is legal to use array names as
const pointers and vice versa, so
MotleyCrew + 4 is a legitimate way of accessing the data at
MotleyCrew[4]
. The compiler does all the arithmetic when adding, incrementing and decrementing pointers. The address accessed when writing
Family+4 isn't 4 bytes past the address of Family, it is 4 objects. If each object is 4 bytes long, then Family+4 is 16 bytes.
Below is an example of declaring and using an array on the free store:
#include <iostream.h>
//------------------------------------------------------------------------------------------------------------------
class Monster {
public:
Monster() { itsAge = 1; itsWeight=5; } ~Monster();
//Accessor methods
int GetAge()
const {
return itsAge; }
int GetWeight()
const {
return itsWeight; }
void SetAge(int
age) { itsAge = age; }
private:
int itsAge;
int itsWeight; };
Monster :: ~Monster() { cout << "Destructor called!\n"; }
//------------------------------------------------------------------------------------------------------------------
int main() {
Monster *CookieMonster =
new Monster[500];
int i;
Monster *MonsterPointer;
for(i = 0; i < 500; i++) { MonsterPointer = new
Monster;
MonsterPointer->SetAge(2*i +1); CookieMonster[i] = *MonsterPointer;
delete
MonsterPointer; }
for(i = 0; i < 500; i++) cout << "CookieMonster #" << i+1 << ": " <<
CookieMonster[i].GetAge() << endl;
delete []
CookieMonster;
return 0; } |
Output:
CookieMonster #1: 1
CookieMonster #2: 3 CookieMonster #3: 5 |
CookieMonster
#4: 7
... and so on and so on . . . CookieMonster #499: 997
CookieMonster #500: 999 |
The example above declares
the array CookieMonster, which holds 500 Monster objects.
The entire array is created on the free store with the call to
new Monster[500] . Each
Monster object added to the array is also created on the free store(heap). The pointer to the object isn't added to the array this time, the object itself is. This isn't an array of pointers to
Monsters, it is an array of Monsters on the heap pointed to by a
single pointer - CookieMonster.
Memory consumed by the entire array is cleared with "delete".
Deleting arrays on the free store - CookieMonster is a pointer to the array
of Monster objects on the free store. The pointer,
MonsterPointer, is dereferenced and the Monster object itself is stored in the array (the array is on the free store, not the stack, so why not?)
MonsterPointer is used again in the next iteration of the loop. Isn't there a danger that there will now be no pointer to the
Monster object and a memory leak will occur? This would be a problem, but deleting
the single pointer, CookieMonster, frees all the memory set aside for the array
of 500 Monster objects on the heap. The compiler destroys each object on the array and restores its memory to the free store. To see this, change the size of the array from 500 to 10,
then re-compile and run the program. Then uncomment the cout statement
in the destructor implementation. When the end of main is reached and the array is destroyed, each
Monster object destructor is called.
Note: When you create an item on the heap by using "new", you always delete the item and free its memory with "delete". Likewise, when you create an array using "new + class + [size]", you must delete the array and free all its memory with "delete[ ]". The brackets signal to the compiler that this
entire array is being deleted. If you leave the brackets off, only the first object in the array will be deleted.
The others will continue to exist and consume memory.
String - a series of characters, an array of
variables of type char. Example of
an unnamed string constant:
cout << "Hello world.\n";
This brings us to char arrays. In C++, a string is an array of chars ending with a null character. You declare and initialize a string as you do any other array:
char Greeting[ ] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0' };
The last character '\0', is the null character, which many functions recognize as the terminator for the string.
So each character in a char array is stored in a separate
element. You can use a shorthand mode to auto-allocate:
char Greeting[ ] = "Hello World";
You don't need to add the null character, the compiler adds it for you. The string "Hello World" is 12 bytes. Hello is 5, the space is 1, and World is 5. Don't place more into the buffer
than there is room for. Example:
#include <iostream.h>
int main() {
char buffer[80]; cout << "Enter the string: "; cin >> buffer; cout << "Here's the buffer: " << buffer << endl;
return 0; } |
Output:
Enter the string: Hello World Here's the buffer: Hello
Above, the char array, buffer[80], is declared to hold 80 characters, large enough for
a 79 character string and the additional terminating null character.
The user prompted to enter a string. The user-entered string is entered into the buffer[80]. It is the syntax of cin to write a terminating null to the buffer after it writes the string.
There are two outstanding problems here. First, if user enters more than 79 characters, cin writes past the end of the buffer
and will overwrite what they previously entered. Second, if
the user enters a space, cin thinks it is the end of the string and stops writing to the buffer[]. To solve these problems, call a special method on cin - get().
The method cin.get() takes 3 parameters:
1) The buffer to fill
2) The max # of characters to get
3) The delimiter that terminates input. (The default delimiter is
newline.)
#include <iostream.h>
int main() {
char buffer[80]; cout << "Enter the string: ";
cin.get(buffer, 79);
//get up to 79 or newline
cout << "Here's the buffer: " << buffer << endl;
return 0; } |
Output:
Enter the string: Hello World Here's the buffer: Hello World
Here buffer is again declared. For input, a call to
the method cin.get() is
placed. The array declared buffer[] is passed in as the
first argument. The number of characters, 79, is passed in as
the second argument. For the 3rd argument, there is no need to provide a terminating character because
the default value of "newline" is sufficient. Now
let's look a t a few String class functions:
strcpy() - The strcpy()
method copies the entire contents of one string into a designated buffer.
To use strcpy(), remember that you must include the string.h
header file into your source code. Example:
#include <iostream.h>
#include <string.h>
int main() {
const
int MaxLength = 80;
char String1[] = "Fun with LEGOS!";
char String2[MaxLength+1];
strcpy(String2,String1);
cout << "String1: " << String1 << endl; cout << "String2: " << String2 << endl;
return 0; } |
Output:
String1: Fun with LEGOS! String2: Fun
with LEGOS!
In this example, the method strcpy() takes 2 character arrays,
the destination to copy to followed by the source being copied
from. If the source were larger than the destination,
strcpy would overwrite past the end of the buffer. To protect against
this, the standard library string.h includes the method
strncpy().
strncpy() - The strncpy()
method takes the maximum number of characters to copy, and copies up to the first null character or
to the max # of characters specified in the destination buffer. Example:
#include <iostream.h>
#include <string.h>
int main() {
const
int MaxLength = 80;
char String1[] = "Fun with LEGOS!";
char String2[MaxLength+1];
strncpy(String2,String1,MaxLength);
cout << "String1: " << String1 << endl; cout << "String2: " << String2 << endl;
return 0; } |
Output:
String1: Fun with LEGOS! String2: Fun
with LEGOS!
Let's list some of these "string" char array functions:
1) cin.get() - captures a whole line of input, including
spaces.
2) strcpy() - copies one string to another.
3) strncpy() - copies up to a specified length of a
string.
4) getline() - captures a whole line of input, including
spaces.
String classes - standard component of a class library. C++ inherited strcpy() and strncpy() from C, but they don't fit into object oriented programming
philosophy as well as string class methods. A String class provides an encapsulated, hidden set of data and functions.
Note: It is worth noting the limitation that all arrays, char arrays included, are
static. A good String class should
be dynamic and allocate only as much memory as it needs: always enough,
yet not too much. If it can't allocate enough memory, it should fail gracefully.
Search Routines Using
Arrays
Remember that part about arrays and loops possessing vast
search capabilities? Let's look at some examples.
The first is a typical linear search. In this example, we
will cycle through all the elements in an array, looking for a
particular item until we find it:
//A linear search routine using arrays
#include <iostream.h>
//------------------------------------------------------------------------------------------------------------------
int LinearSearch(int *ArrayPointer, int key, int NItems)
{
int n;
for(n=0; n<NItems; ++n)
if (ArrayPointer[n] == key)
return n;
return -1;
}
//------------------------------------------------------------------------------------------------------------------
void SearchInputAndValidate(int *ArrayPointer, int NumItems) {
int ValueToSearchFor;
int ArrayElement;
cout << "Enter a number between 1 and " << NumItems << ": ";
cin >> ValueToSearchFor;
ArrayElement = LinearSearch(ArrayPointer, ValueToSearchFor, NumItems);
if(ArrayElement != -1)
cout << "Found value in element " << ArrayElement << ".\n\n";
else
cout << "Sorry, value not found.\n\n";
}
//------------------------------------------------------------------------------------------------------------------
void main() {
int x;
int NumberOfItems;
NumberOfItems = 100;
int *ArrayOfIntegers = new int[NumberOfItems];
//Populate Array with even integer values from 0 to 198
for(x=0; x<NumberOfItems; ++x) {
ArrayOfIntegers[x] = 2 * x; }
SearchInputAndValidate(ArrayOfIntegers, NumberOfItems);
} // close main()
|
In this
next example we use a binary search algorithm, in which case
we continuously halve the range where we look for our value.
While not impressive on a small scale, on a large scale this
would dramatically reduce the amount of time it takes to
search through elements in an array. In a linear
search, element 999,999 in a 1,000,000 elements array would
only be found after searching through 999,998 elements, as
the loop iterates scanning the elements one by one. In
a binary search, 1,000,000 elements would be split into two
sets: one set = 1 - 500,000 and the other set = 500,001 -
1,000,000. The item being searched for would then be
compared to both ranges and depending on its relationship (<
or >), one of the half ranges would be selected and it would
in turn be halved and so on and so on. In this way, we
could reach item 999,998 in a few dozen or so iterations, rather
than 999,999. Example:
//A binary search using arrays
#include <iostream.h>
//------------------------------------------------------------------------------------------------------------------
int BinarySearch(int *ArrayPointer, int key, int NItems)
{
int n;
int High = NItems;
int Low = -1;
int Range;
while(High-Low > 1)
{
Range = (High + Low) / 2;
if(ArrayPointer[Range] > key)
High = Range;
else
Low = Range;
} //end while loop
if(Low == -1 || ArrayPointer[Low] != key)
return -1;
else
return Low;
} //close function
//------------------------------------------------------------------------------------------------------------------
void SearchInputAndValidate(int *ArrayPointer, int NumItems) {
int ValueToSearchFor;
int ArrayElement;
cout << "Enter a number between 1 and " << NumItems << ": ";
cin >> ValueToSearchFor;
ArrayElement = BinarySearch(ArrayPointer, ValueToSearchFor, NumItems);
if(ArrayElement != -1)
cout << "Found value in element " << ArrayElement << ".\n\n";
else
cout << "Sorry, value not found.\n\n";
}
//------------------------------------------------------------------------------------------------------------------
void main() {
int x;
int NumberOfItems;
NumberOfItems = 100;
int *ArrayOfIntegers = new int[NumberOfItems];
//Populate Array with even integer values from 0 to 198
for(x=0; x<NumberOfItems; ++x) {
ArrayOfIntegers[x] = 2 * x; }
SearchInputAndValidate(ArrayOfIntegers, NumberOfItems);
} // close main()
|
More multi-dimensional
array examples:
//
Multidimensional Array
#include <iostream>
using namespace std;
int main()
{
int sales[6][2] = {{12000, 10000},
{45000, 56000},
{32000, 42000},
{67000, 23000},
{24000, 12000},
{55000, 34000}};
int
row = 0;
int
DomesticSales = 0;
int
InternationalSales = 0;
while(row < 6)
{
DomesticSales = DomesticSales + sales[row][0];
InternationalSales = InternationalSales + sales[row][1];
row = row
+ 1;
}
cout << "Total domestic sales: $" <<
DomesticSales << endl;
cout << "Total international sales: $" <<
InternationalSales << endl;
cout << "Total sales: $" <<
DomesticSales + InternationalSales <<
endl;
return 0;
} |
//Calculates highest score on midterm and final
#include <iostream>
using namespace std;
int main()
{
int scores[10][2] = { {98, 99}, {67, 54}, {78, 43},
{90, 86}, {88, 56},
{78, 56},
{87, 88}, {72, 96}, {44, 67},
{66, 34} };
int
row = 0;
int col
= 0;
int midHigh
= 0;
int finalHigh
= 0;
midHigh = scores[0][0];
finalHigh = scores[0][1];
while(row < 10)
{
col = 0;
//reset column subscript
while(col < 2)
{
if(scores[row][0] > midHigh)
midHigh = scores[row][col];
if(scores[row][1] > finalHigh)
finalHigh = scores[row][col];
col = col + 1;
//update column subscript
}
//end 2nd while
row = row + 1; //update row subscript
}
//end
1st while
cout << "Highest score earned on midterm: " <<
midHigh << endl;
cout << "Highest score earned on final: " <<
finalHigh << endl;
return 0;
} |
Let's determine our gas mileage:
// Calculating gas mileage
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
const int MAX = 20; // Maximum number of values
double gas[ MAX ]; // Gas quantity in gallons
long miles[ MAX ]; // Odometer readings
int count = 0; // Loop counter
char indicator = 'y'; // Input indicator
while( (indicator == 'y' || indicator == 'Y') && count < MAX )
{
cout << endl
<< "Enter gas quantity: ";
cin >> gas[count]; // Read gas quantity
cout << "Enter odometer reading: ";
cin >> miles[count]; // Read odometer value
++count;
cout << "Do you want to enter another(y or n)? ";
cin >> indicator;
}
if(count <= 1) // count = 1 after 1 entry completed -
{ // we need at least 2.
cout << endl
<< "Sorry - at least two readings are necessary.";
return 0;
}
// Output results from 2nd entry to last entry
for(int i=1; i < count; i++)
cout << endl
<< setw(2) << i << "." // Output sequence number
<< "Gas purchased = " << gas[ i] << " gallons" // Output gas
<< " resulted in " // Output miles per gallon
<< (miles[i] - miles[i-1])/gas[i] << " miles per gallon.";
cout << endl;
return 0;
}
|
Shortcut way to
initialize arrays:
|
int AnArray[5000] =
{ 0 }; //Initializing
the first element will initialize all 5000 to 0 |
Strings via
pointer to char arrays:
// Initializing pointers with strings
#include <iostream>
using namespace std;
int main()
{
char * pstr1 = "Robert Redford";
char * pstr2 = "Hopalong Cassidy";
char * pstr3 = "Lassie";
char * pstr4 = "Slim Pickens";
char * pstr5 = "Boris Karloff";
char * pstr6 = "Oliver Hardy";
char * pstr = "Your lucky star is ";
int dice = 0;
cout << endl
<< " Pick a lucky star!"
<< " Enter a number between 1 and 6: ";
cin >> dice;
cout << endl;
switch(dice)
{
case 1: cout << pstr << pstr1;
break;
case 2: cout << pstr << pstr2;
break;
case 3: cout << pstr << pstr3;
break;
case 4: cout << pstr << pstr4;
break;
case 5: cout << pstr << pstr5;
break;
case 6: cout << pstr << pstr6;
break;
default: cout << "Sorry, you haven't got a lucky star.";
}
cout << endl;
return 0;
}
|
// Initializing an array of char pointers
#include <iostream>
using namespace std;
int main()
{
char * pstr[] = { "Robert Redford",
"Hopalong Cassidy",
"Lassie",
"Slim Pickens",
"Boris Karloff",
"Oliver Hardy"
};
char* pstart = "Your lucky star is ";
int dice = 0;
cout << endl
<< " Pick a lucky star!"
<< " Enter a number between 1 and 6: ";
cin >> dice;
cout << endl;
if(dice >= 1 && dice <= 6) // Check input validity
cout << pstart << pstr[dice-1]; // Output star name
else
cout << "Sorry, you haven't got a lucky star."; // Invalid input
cout << endl;
return 0;
}
|
Using
sizeof() to compute the number of elements in an
array:
|
int
NumElements = (
sizeof(pstr)
) / (
sizeof(pstr[0]) )
|
Example:
// Flexible array management using sizeof
#include <iostream>
using namespace std;
int main()
{
const char* pstr[] = { "Robert Redford", // Initializing a pointer array
"Hopalong Cassidy",
"Lassie",
"Slim Pickens",
"Boris Karloff",
"Oliver Hardy"
};
char* pstart = "Your lucky star is ";
int count = (sizeof pstr)/(sizeof pstr[0]); // Number of array elements
int dice = 0;
cout << endl
<< " Pick a lucky star!"
<< " Enter a number between 1 and " << count << ": ";
cin >> dice;
cout << endl;
if(dice >= 1 && dice <= count) // Check input validity
cout << pstart << pstr[dice-1]; // Output star name
else
cout << "Sorry, you haven't got a lucky star."; // Invalid input
cout << endl;
return 0;
}
|
Note
on using const
with char
pointer strings:
A string
literal is a const char array, so once we
assign it, we can not alter it. This
holds true even if we do not declare the
pointer to char to be constant. If we
therefore try to change a pointer to a char
array that was initialized with a string
literal, we will crash our program.
Example:
char *
CharArray = { "Test 1", "Test 2", "Test
3" };
*CharArray[0] = 'A';
//This
reassignment should crash the program
It is
therefore safer to write:
const
char *
CharArray = { "Test 1", "Test 2", "Test
3" };
This
would declare the strings to be
constant. This would not prevent
the pointer being reassigned to
different elements, however.
Therefore, it is also useful to write:
const
char *
const
CharArray = { "Test 1", "Test 2", "Test
3" };
The
statement above indicates that not only
are the char arrays (strings) constant,
but the pointers as well - they can not
be reassigned.
When
using pointers and arrays together, you can
increment or decrement the array by
performing arithmetic operations with the
pointer. Example - please take note:
pIntPointer += 1;
//increments
pIntPointer to the next array element
pIntPointer++;
//increments
pIntPointer to the next array element
So, if pIntPointer points to
ArrayOfIntegers[5],
then the expression:
*(pIntPointer + 1)
= *(pIntPointer + 2);
is equivalent to the expression:
ArrayOfIntegers[6]
= ArrayOfIntegers[7];
An array
name can be used exactly as though it were a
pointer to itself, such that given the
declaration:
int ArrayOfIntegers[100];
The
element
ArrayOfIntegers[50];
can also
be referred to as:
*(ArrayOfIntegers + 50);
Example of Array names as pointers:
// Calculating primes using dynamic memory allocation
#include <iostream>
#include <iomanip>
#include <cstdlib>
using namespace std;
int main()
{
long* pprime=0; // Pointer to prime array
long trial = 5; // Candidate prime
int count = 3; // Count of primes found
int found = 0; // Indicates when a prime is found
int max = 0; // Number of primes required
cout << endl
<< "Enter the number of primes you would like: ";
cin >> max; // Number of primes required
if(!(pprime=new long[max]))
{
cout << endl
<< "Memory allocation failed.";
exit(1); // Terminate program
}
*pprime = 2; // Insert three
*(pprime+1) = 3; // seed primes
*(pprime+2) = 5;
do
{
trial += 2; // Next value for checking
found = 0; // Set found indicator
for(int i = 0; i < count; i++) // Division by existing primes
{
found =(trial % *(pprime+i)) == 0; // True for exact division
if(found) // If division is exact
break; // it's not a prime
}
if (found == 0) // We got one...
*(pprime+count++) = trial; // ...so save it in primes array
} while(count < max);
// Output primes 5 to a line
for(int i = 0; i < max; i++)
{
if(i%5 == 0) // New line on 1st, and every 5th line
cout << endl;
cout << setw(10) << *(pprime+i);
}
delete [] pprime; // Free up memory
cout << endl;
return 0;
}
|
©2004 C. Germany
|