Reading: Deitel & Deitel, Chap 4.9, 5.9, 5.10
Two dimensional arrays
C++ allows you to create two dimensional arrays, as shown in the following
example:
1 int main(){
2 int c = 1,
3 r = 0;
4 double arr[2][3];
5 arr[r][c] = 3.141592;
6 return 0;
7 }
void Fctn(double arr[][3])
1 #include <iostream>
2 using namespace std;
3
4 void fill_table(int table[][10]){
5 for (int row = 0; row < 10; ++row){
6 for (int col = 0; col < 10; ++col){
7 table[row][col] = row * col;
8 }
9 }
10 }
11
12 void print_table(int table[][10]){
13 for (int row = 0; row < 10; ++row){
14 for (int col = 0; col < 10; ++col){
15 cout << ' ' << table[row][col];
16 }
17 cout << endl;
18 }
19 }
20
21 int main(){
22 int table[10][10];
23
24 fill_table(table);
25 print_table(table);
26 return 0;
27 }
Exercise 1: Write a program which prompts the user to type in five character strings into the array char input[5][32]. Use a loop to input the five strings. Then pass this array as a parameter to a function named uppers, which will count the number of uppercase characters entered in each of the five strings, and return that number to main. Print out the value from the function and return.
Strings
One of the most confusing things about pointers is that they
seem to refer to both addresses and arrays. This confusion is
most common with strings. Recall that a string is an array
of characters terminated with '\0'.
Consider this line of code:
char *p = "This is a string";
The variable p contains an address in memory, 30,000 for example.
The byte at address 30,000 contains 84 (ASCII code for 'T'),
the byte at address 30,001 contains 104 (ASCII code for 'h'),
the byte at address 30,002 contains 105 (ASCII code for 'i'),
the byte at address 30,003 contains 115 (ASCII code for 's'),
the byte at address 30,004 contains 32 (ASCII code for a space),
and so on. The byte at address 30,016 contains '\0'. The
contents of memory location 30,017 and beyond are indeterminate.
However, note that the variable p contains only a single value, 30,000, the address of the head of the string.
You can print out a string using cout by using the address of the string as the operand.
cout << p << endl;
prints the line This is a string on the terminal.
When you pass a pointer as an argument to a function, only the address is copied. However, the function can change the contents of the memory that the pointer is pointing to. Here is an example:
#include <iostream>
using namespace std;
void makeupper(char *); // function prototype
int main()
{
char *p = "This is a string";
char q[80];
strcpy(q,p);
makeupper(q);
cout << q << endl;
return 0;
}
void makeupper(char *s)
{
while (*s != '\0') {
if (*s >= 'a' && *s <= 'z')
*s -= 32;
s++;
}
}
Arrays of Pointers
It is possible to create arrays of pointers. Typically these
are arrays of pointers to characters (i.e. arrays of character
strings), but arrays of pointers to any data type are permitted.
The following statement declares an array s of five pointers to
character strings:
char *s[5];
Note that after this declaration, none of these pointers have
been initialized. However, they can be assigned in ways we've already
seen. Here are some examples with a memory diagram:
Note also that the following statement would cause a memory exception error:
strcpy(s[0],"Happy");
These arrays can be initialized by enclosing the strings in
curly braces, separated by commas. Here is an example:
char *dwarfs[] = {"Happy","Grumpy","Sneezy","Sleepy",
"Bashful","Doc","Dopey"};
Note that when an array is initialized in this way, it is
not necessary to specify the size of the array. The implied number
in the square brackets is 7 in the above example because there are
seven character strings (dwarfs).
You could print Grumpy with this statement:
cout << dwarfs[1];
In some ways, an array of pointers is like a two-dimensional array.
For example, you could print the p in
Grumpy like this:
cout << dwarfs[1][4];
You could also print the p in Grumpy like this:
cout << *(dwarfs[1]+4);
This statement is pretty ugly, and most people don't write code that looks like this, but it is instructive. To analyze it, start from inside the parentheses. dwarfs[1] is a character pointer because dwarfs is an array of character pointers. Suppose this address is 500,000. This means that the character G is at memory address 500,000, the character r is at memory address 500,001, the character u is at memory address 500,002, and so on. The expression dwarfs[1] + 4 returns an address four bytes higher than dwarfs[1], in other words, the memory address 500,004. The * operator takes a pointer as an operand, goes to that address and gets the value, in this case the character p.
Since an array variable is really a pointer, an array of pointers
can be thought of as a pointer to a pointer. (In fact, you will sometimes
see char *varname[]; written as char **varname;.)
Here is a third, even uglier way to print the p in Grumpy,
that uses no array brackets at all:
cout << *(*(dwarfs+1)+4);
Exercise 2: What would the following program print? If the program would generate either compile time errors or run time errors, indicate this and explain the problem, but then continue on as if the error had not occurred. (Answer at the end of the worksheet.)
#include <iostream>
using namespace std;
int main()
{
char *s[6];
s[0] = "Mickey Mouse";
s[1] = "Donald Duck";
strcpy(s[2],s[0]);
s[3] = new char[30];
strcpy(s[3],s[1]);
s[4] = s[3];
char *p = s[1];
cout << p << endl;
p += 2;
cout << p << endl;
p = s[3];
p += 2;
for (int i = 0; i < 6; ++i)
cout << *(p++);
cout << endl;
*p = 'X';
cout << s[3] << endl;
cout << *s[0] << endl;
cout << (int) *(s[0]+6) << endl;
char **pp;
pp = s;
pp++;
cout << *pp << endl;
return 0;
}
Pointer Tricks and Traps
Many of the hard-to-find bugs in programs that you write in the future will result from the misuse of pointers. Since array indexing in C++ gives no indication when you reach the end of the array, it is very easy to overwrite other variables. Here is a simple example where the strcat function is misused (recall that strcat concatenates the string in its second argument onto the end of the string in its first argument):
#include <iostream>
using namespace std;
int main()
{
int i;
char *dwarfs[] = {"Happy", "Grumpy", "Doc", "Sleepy", "Bashful"};
for (i = 0; i < 3; ++i)
cout << dwarfs[i] << endl;
cout << endl;
strcat(dwarfs[1]," loves Snow White");
for (i = 0;i < 3; ++i)
cout <<dwarfs[i] << endl;
return 0;
}
You would expect the output of this program to look like this:
Happy Grumpy Doc Happy Grumpy loves Snow White Doc
Happy Grumpy Doc Happy Grumpy loves Snow White loves Snow White
Notice that the string dwarfs[2] has been changed although
there was no statement changing it. This is because the string
pointed to by dwarfs[1] was only seven (not six!) characters
long, so when you concatenated another string onto it, other memory
was overwritten. It's not guaranteed that the memory for dwarfs[2] immediately follows that of dwarfs[1], but most
compilers will allocate memory in this way.
Assuming that the strings are stored contiguously, the memory layout of dwarfs would look something like this:
If the value of dwarfs[0] (i.e. the start of the string Happy) is address 5000, then
the value of dwarfs[1] is 5006, the value of dwarfs[2]
is 5013 and so on. When you concatenate the string "loves Snow
White" onto the string Grumpy, you will overwrite Doc,
Sleepy and part of Bashful.
No error or warning messages would be produced, and you might not even have discovered the error until much later, and so finding this bug would have been extremely difficult.
Another common type of error occurs when you use the assignment operator = on a string when you should use strcpy. The assignment operator on pointers makes both pointers point to the same memory, and so subsequent changes to the contents of this memory appears to change both values. Here is an example of erroneous code which demonstrates this. The program asks the user to enter three strings, and it stores these three strings in an array called storage. It then prints the three strings.
#include <iostream>
using namespace std;
int main()
{
char *in = new char[80];
char *storage[3];
int i;
for (i = 0; i < 3; ++i) {
cout << "Enter a string: ";
cin >> in; // user enters a character string
storage[i] = in; // save the string
}
for (i = 0; i < 3; ++i)
cout << storage[i] << endl;
return 0;
}
Here is a correct implementation of this program.
#include <iostream>
using namespace std;
int main()
{
char *in = new char[80];
char *storage[3];
int i;
for (i = 0; i < 3; ++i) {
cout << "Enter a string: ";
cin >> in;
storage[i] = new char[strlen(in)+1]; // Why must we add 1 here?
strcpy(storage[i],in);
}
for (i = 0; i < 3; ++i)
cout << storage[i] << endl;
return 0;
}
Exercise 3: Modify the code you wrote for Exercise 1 in two steps (finish the first before trying the second):
Yet a third common type of error involves the == operator on
pointers. Novice programmers often use this to test whether two
strings have the same contents, when they should be using the
strcmp(char *s1, char *s2) function. The == operator
on pointers checks to see if the two pointers are pointing to the
same memory address.
Here is an example:
#include <iostream>
using namespace std;
int main()
{
char *s1 ="dog";
char *s2 = new char[4];
strcpy(s2,s1);
if (s1 == s2)
cout << "True" << endl;
else
cout << "False" << endl;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
char *s1 ="dog";
char *s2 = new char[4];
strcpy(s2,s1);
if (strcmp(s1,s2) == 0)
cout << "True" << endl;
else
cout << "False" << endl;
return 0;
}
Exercise 4: What would the following program print? If there are statements that would cause errors, please indicate this and continue as if the statement were not there. (Solution is at the end of the worksheet.)
#include <iostream>
using namespace std;
int main()
{
char *ptrs[6];
ptrs[0] = new char[64];
strcpy(ptrs[0],"abcdefg");
ptrs[1] = "Here is a typical string of characters";
ptrs[2] = ptrs[0];
strcpy(ptrs[2],ptrs[1]);
cout << ptrs[0] << endl;
ptrs[3] = new char [16];
ptrs[4] = "Another string";
strcpy(ptrs[3],ptrs[1]);
strcpy(ptrs[5],ptrs[4]);
strcpy(ptrs[0],ptrs[1]);
cout << ptrs[0]+5 << endl;
if (ptrs[1] == ptrs[0])
cout << "True" << endl;
else
cout << "False" << endl;
if (ptrs[0] == ptrs[2])
cout << "True" << endl;
else
cout << "False" << endl;
ptrs[6] = new char[16];
strcpy(ptrs[6],"12345");
return 0;
}
Passing arguments to programs, argc and argv
When a program is run from a console window by typing the command, it is possible to pass arguments to your program 1These arguments are separated by one or more white spaces. So far we have written main as if it had no arguments, but in fact it has two arguments. The first argument is of type int and is always called argc, which stands for argument count, the number of arguments, including the name of the executable itself. The second argument is an array of character pointers which point to the arguments. By convention, this is always named argv for argument vector. For example, if you typed myprog.exe hello world, argc would have the value 3, argv[0] would be "myprog.exe", argv[1] would have the value "hello", and argv[2] would have the value "world".
Here is a program which simply prints out its arguments.
Compile and run it with various arguments.
#include <iostream>
using namespace std;
main(int argc, char *argv[])
{
for (int i = 1;i<argc;i++)
cout << argv[i] << " ";
cout << endl;
}
You can pass arguments to your programs when you run them in Visual Studio by choosing Settings from the Project menu, (Alt-F7), and choosing the Debug tab. You can type your arguments in the Program Arguments window.
Exercise 5:
Write a program that takes any number of arguments and displays the
number of vowels in all of them (excluding 'y'). For example,
the following command:
a.out Grumpy Sleepy Happy Doc
would display
There were 5 vowels
Solution to Exercise 2:
The line strcpy(s[2],s[0]); will cause a run time error
because s[2] does not point to valid memory.
When this line is deleted, the output looks like this:
Donald Duck nald Duck nald D Donald DXck M 32 Donald Duck
Solution to Exercise 4:
The line strcpy(ptrs[5],ptrs[4]); will generate an error
since ptrs[5] does not point to valid memory. The lines
strcpy(ptrs[3],ptrs[1]); and ptrs[6] = new
char[16]; are not necessarily error-producing. However, they
could produce errors, depending on where the compiler allocated
the various pieces of memory. Removing those lines, the output is:
Here is a typical string of characters is a typical string of characters False True