CSCE 121: Introduction to Program Design and Concepts
Lab Exercise Nine
Objective
The purpose of this lab is to help clarify the relationship between pointers and arrays; you will look at arrays of pointers, pointers to arrays, and the C/C++ treatment of multi-dimensional arrays.
A difference this week
This week's lab is a little different from prior labs. There is very little code to write. (Actually only about four lines of C++.)
Instead, this lab provides example code and you're expected to work through it, poking at it to try make sense of what's going on. The text should guide you in that process. By "make sense" I mean that you should be able to draw a diagram showing blocks in memory, and arrows for pointers like I've done in the lectures.
Pointers to elements within an array
Copy and paste this into your editor, then build the code and run the result.
This program has an array of char
s that stores
a string. That string is passed to two functions, which are
effectively identical: they print their sole argument.
The functions differ only in the type that they expect that argument to be.
#include <iostream>
using namespace std;
void bracket_function(char str[])
{
// This line will print out what is passed on the call-stack to the function:
//C1: cout << "str = " << (void *)str << endl;
cout << "Bracket Function: ";
cout << str << endl;
}
void star_function(char *str)
{
// This line will print out what is passed on the call-stack to the function:
//C2: cout << "str = " << (void *)str << endl;
cout << "Star Function: ";
cout << str << endl;
}
int main()
{
char lyric[] = "...there's a Spirit can ne'er be told...";
star_function(lyric); // L1: Maybe this is a trifle surprising?
bracket_function(lyric);
star_function(&lyric[0]);
bracket_function(&lyric[0]); // L2: And also this?
// These two show how it can actually be rather useful:
// C3: star_function(&lyric[32]);
// C4: bracket_function(&lyric[32]);
return 0;
}
As we've seen in class, arrays degrade to pointers, so the functions are equivalent.
- The array can be passed to the function expecting a pointer (see line
L1
). - A pointer can be passed the function expecting an array (see line
L2
). (The pointer is just the address of the first element of the array.) - Uncomment
C1
andC2
, and see what it does. The input is cast (like we've seen forint
s andfloat
s) in order to lose the typing information. We do this becausecout
, by default, will show what is at the address when you give it achar[]
orchar *
. Put another way: ifcout
receives a string, it will automatically dereference it. To print the actual address, not what is at that address, we pretend that it doesn't containchar
s. (If you find thevoid
weird, you can make it anint
or some other type, too.) - Next, uncomment
C3
. Before you run the result, think about the addresses being passed and form an hypothesis of what you think it'll do. Did you get it? - Finally, uncomment
C4
. Will that compile?
If any of the preceding is troubling to you, try to draw a memory diagram.
Look at the memory addresses; does the numerical difference make sense to you?
How would it differ if the arrays were of int
s, float
s, or some sort of struct
?
Arrays with pointers to arrays
Copy and paste the following code into your editor, then build the code and run the result.
In class, we (briefly) examined an example where there was something with a type with two asterisks. We can think of these as arising via pointers to pointers, arrays of pointers, and arrays of arrays. Unlike many other languages C/C++ does not allocate a sequential block for multi-dimensional arrays. Instead, in the innermost level it has standard 1D arrays. The second dimension is a 1D array of pointers. The following code is intended to help clarify how this works.
#include <iostream>
using namespace std;
int main()
{
float row_one[2] = {3.0, 1.4};
float row_two[3] = {4.5, 7.2, 5.6};
float row_three[4] = {15.6, 18.4, 22.2, 105.0};
float *different_sized[3] = {row_one, row_two, row_three}; // D1
cout << "different_sized := " << endl;
cout << "\t" << different_sized[0][0] << "\t" << different_sized[0][1] << endl;
cout << "\t" << different_sized[1][0] << "\t" << different_sized[1][1] << "\t" << different_sized[1][2] << endl;
cout << "\t" << different_sized[2][0] << "\t" << different_sized[2][1] << "\t" << different_sized[2][2] << "\t" << different_sized[2][3] << endl << endl;
float **synonym = different_sized; // that's also a pointer to a pointer
cout << "synonym := " << endl;
cout << "\t" << synonym[0][0] << "\t" << synonym[0][1] << endl;
cout << "\t" << synonym[1][0] << "\t" << synonym[1][1] << "\t" << synonym[1][2] << endl;
cout << "\t" << synonym[2][0] << "\t" << synonym[2][1] << "\t" << synonym[2][2] << "\t" << synonym[2][3] << endl << endl;
return 0;
}
An advantage of the C/C++ method is that all the elements within a certain
dimension needn't be of the same size. This is illustrated by with
1D variables named row_one
, row_two
, and row_three
.
The 2D array different_sized
is declared to have 3 elements, each
of which we set up to point to each row. It is useful to draw a memory diagram,
the row
s should just be data stored linearly, and the
different_sized
elements should have three arrows departing it.
We can see that indexing works left to right: different_sized[i][j]
first
accesses the pointer stored in the ith
entry of different_sized
. The pointer is followed, giving an array.
In that array, the jth element is accessed. (It might be useful
to follow this two-step
process on your diagram.)
In the previous question, we saw that a char[]
is equivalent to
a char *
. That is general, so for any type
T
we have that
T[] = T*.
Now consider the definition on line D1
which
reads float *different_sized[3]
. If we think of that as
(float *) different_sized[3]
, then we can think of a
type float **
. That is why the code defining synonym
works similarly. The two layers of indexing essentially become a double dereferencing operation.
You might find it helpful to print different_sized
and
synonym
to check their memory addresses. You can also
check the addresses after one level of indexing.
But note that &different_sized
≠
&synonym
, which are of type float ***
.
|
|
Arrays of pointers
Your job in this question is to do a bit of detective work to understand how this program operates. Start with the following code, which should compile and run out of the box. It provides an example of how thinking of arrays as pointers can actually be rather handy.
#include <iostream>
using namespace std;
bool valid_word_char(char ch)
{
if (('A' <= ch) && ('Z' >= ch)) return true;
if (('a' <= ch) && ('z' >= ch)) return true;
if (('0' <= ch) && ('9' >= ch)) return true;
if (('\'' == ch) || ('.' == ch)) return true;
return false;
}
void print_single_word(char str[])
{
int i = 0;
while (valid_word_char(str[i]))
cout << str[i++];
}
int calc_word_length(char str[])
{
int i = 0;
while (valid_word_char(str[i]))
i++;
return i;
}
char *increment_word(char str[])
{
int i = 0;
while (valid_word_char(str[i]))
i++; // Pass through word
while ((str[i] != '\0') && (!valid_word_char(str[i])))
i++; // To next word
return &str[i]; // This is a pointer to the next word
}
int main()
{
const int max = 200;
char sentence[max] = "The rhino is a homely beast,\nFor human eyes he's not a feast.\nFarewell, farewell, you old rhinoceros,\nI'll stare at something less prepoceros."; // (of Ogden Nash)
char *words[max];
cout << sentence << endl;
int w = 0;
words[0] = sentence;
while (words[w][0] != '\0') {
words[w+1] = increment_word(words[w]);
w++;
}
cout << "---- Total words w = " << w << " -------" << endl;
for (int i = 0; i < w; i++) {
print_single_word(words[i]);
cout << endl;
}
return 0;
}
If you run it, you'll see what it does.
-
To understand how it does what it's doing, first look at the function
print_single_word
. I wrote that rather than just callingcout
, can you see why? (If not, putcout
in the code and see what it does.) - Though the
calc_word_length
function isn't called, looking at it can help see how the string is being processed. - It can help to draw a diagram.
Mine has
a series of characters for
sentence
and then another with a series of boxes, each as the source for an arrow, forwords
. If you step through a short example, you'll get the gist of how those arrows get set.
Acknowledgements
The lyric is, of course, from The Spirit of Aggieland. The poem in the penultimate question is Ogden Nash's creation.