| Name: _____________________ | Class: Comp 217 |
| SSN/ID: _____________________ | Section & Group: ____________ |
Highlights of this lab:
In this lab, you will:
You won't always know the specific value in a pointer, but you won't care as long as it contains the address of the variable you are after. You need to declare and initialize pointers just as you would other variables, but there are special operators that you need to use.
|
|
dereferencing operator, indirection operator |
This is used
to declare
a variable as a pointer.
It is also used when you want to access the value pointed to by the pointer variable. |
|
|
reference operator, address-of operator |
Use before a variable to indicate that you mean the address of that variable. You'll often see this in a function header where the parameter list is given. |
|
|
member selection operator | This is used to refer to members of structures |
First, we'll declare two ordinary integers, and also pointers to those integers.
int alpha = 5; int beta = 20; int *alphaPtr = α int *betaPtr = βThe characters Ptr in the pointer variable name have no special significance. They are simply a memory aid for the programmer. Let's look more closely at one of the pointer declarations.
int *alphaPtr = α
The first part int *, tells the compiler to declare a
pointer to integers. alphaPtr will be the name of that
pointer. In the last part of that statement, α
specifies that the address of the variable
alpha is what should be assigned to the pointer variable.
An aside here: It is also permissible to position the asterisk closer to the pointer variable name, i.e. int *alphaPtr. However, the convention seems to be moving towards placing the asterisk closer to the datatype.
Try to visualize memory after these declarations, thinking of the pointer variable as not having a particular value, simply links to the variables to which they had been assigned.
Now let's look at a trivial example of how to access this data.
*alphaPtr += 5; *betaPtr += 5;After these statements, only the contents of the alpha and beta variables are changed.
You might say, "What's the big deal here? I could just as easily have written this:"
alpha += 5; beta += 5;True, but typically pointers aren't used in such a simple manner. This just illustrates the proper use of pointer syntax.
A great way to keep track of pointers (and avoid getting lost) is to draw a memory map. E.g., another way to approach alpha and alphaPtr above is to think of how the compiler treats them. When you first declare int alpha = 5;, the first thing the compiler does is setup a memory map as follows:
| Name | alpha | alphaPtr | |
| Value | 5 | 0x03 | |
| Address | 0x03 | 0x09 | ... |
The compiler then:
Now, when we dereference a pointer using the indirection operator (as in, cout << *alphaPtr;), the compiler does the opposite: it first looks up the address stored as the value of the pointer variable (i.e., the value of alphaPtr, which is 0x03); then, it finds the value stored at that address and prints this value out (i.e., it again prints out the value 5).
Let's look at both pass by value and pass by address in addition to pass by reference to make sure we understand the difference between all three. To illustrate how parameters are passed by value, let's take a look at the following:
#include <iostream>
using namespace std;
void exchange (int x, int y);
int main () {
int a = 5;
int b = 9;
cout << "This program attempts to exchange two values." << endl;
cout << "Values before the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
exchange(a, b); // code that calls the function
cout << "Values after the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
}
// function for passing by value
void exchange (int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
} // end exchange
|
Now let's compile this code and run it... did the values of a and b change (please answer Yes or No)? ________________
Simple, direct — right? Well yes, but the values of a and b in the main program have not been changed! If that is what you really wanted to do, you should have used pointers to pass the parameters by address (pointers) or references to pass the parameters by reference.
To do this via pass by address (or pointers), you need to
change the function prototype and header to:
void exchange(int * x, int * y)
exchange(& a, & b)
#include <iostream>
using namespace std;
void exchange (int * x, int * y);
int main () {
int a = 5;
int b = 9;
cout << "This program exchanges 2 values." << endl;
cout << "Values before the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
exchange(&a, &b); // code that calls the function
cout << "Values after the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
}
// function for passing by address (or pointers)
void exchange (int * x, int * y) {
int temp;
temp = *x;
*x = *y;
*y = temp;
return; // Optional for void functions
} // end exchange
|
To do this via pass by reference, you need to
change the function prototype and header to:
void exchange(int & x, int & y)
#include <iostream>
using namespace std;
void exchange (int& x, int& y);
int main () {
int a = 5;
int b = 9;
cout << "This program exchanges 2 values." << endl;
cout << "Values before the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
exchange(a, b); // code that calls the function
cout << "Values after the exchange:" << endl;
cout << "a= " << a << " b= " << b << endl;
}
// function for passing by reference
void exchange (int& x, int& y) {
int temp;
temp = x;
x = y;
y = temp;
return; // Optional for void functions
} // end exchange
|
Now, when the function is executed, the values of a and
b will be changed in the main program.
| new datatype | Used to allocate a dynamic variable.
e.g. int *TmpPtr = new int; |
| delete pointer | Used to deallocate a dynamic variable.
e.g. delete TmpPtr; |
The new operator is used to create dynamic data variables in the so-called free store, available in memory for this purpose. Free space is also referred to as the heap. (Even guru's have been known to let their hair down sometimes.) A couple of points to remember when you use the new statement.
int* TmpPtr = new int;The first part, int* TmpPtr, declares an integer pointer named TmpPtr. The second part, new int, creates a space in the free store, and returns a pointer to that space. The returned address is assigned to TmpPtr. This is typical of C++; i.e. you can accomplish a lot in a single line of code.
You can declare arrays in the free store as well as
simple variables.
E.g., int* WeightPtr = new int [3];
To use this dynamic array element you could code:
WeightPtr[1] = 17; // Note: the "*" is not needed in an array reference.
// But since an array name is simply a
// constant pointer to the starting
// address of that array, you can also
// access the 2nd element with: *(WeightPtr + 1)
We'll see more of this in the programming exercise for this week's lab exercise. The whole idea of using dynamic data is to economize on memory space. So when you've finished with a piece of dynamic data you should release the space with the delete statement.
delete TmpPtr; delete [] WeightPtr;This releases space in the free store, but does not delete the pointer. Be careful here! If you try to use the pointer again, after the delete statement, you don't know what address will be in the pointer. Beware the dreaded segmentation fault, core dump! (See the next section for details.)
To safeguard an inadvertent overwrite of a critical area in memory, it is advisable to set pointers to NULL after you delete the associated dynamic data space. E.g.,
TmpPtr = NULL; WeightPtr = NULL;
Pointers Running Wild!!Segmentation fault - core dump.This is a really ugly way of punting you and your program out of computer memory. Unix guru's might have some hope of decyphering the core file which is supposed to be a dump of the contents of memory (called core way back when the earth was cooling. Yes, C is that old.)
Normal people deal with the core file more simply.
rm coredoes the trick quite nicely. That can be a truly big file, so you don't want it hanging around taking up your disk space.
But what about the problem that caused the dump in the first place?! Well, if you're using pointers in your program, it's likely that one of them took on a bad value. Go back to your source code and look for statements that change pointer values. Remember that when you are in emacs, you can search for a particular string (such as the name of a pointer) by entering: C-s search_string
Let's get right to the exercises!
Examine
the program pointers.cpp. Deduce the values that will be
displayed by the cout statements and enter them in the table
below. Enter and test the code to check. Modify the code and recompile
it until you are happy with basic pointers.
Remember, you can edit (or create) the file pointers.cpp using our editor of choice, Emacs, by typing emacs pointers.cpp & at the unix prompt.
| Line | *p1 | *p2 | x |
| 15 | |||
| 18 |
/* pointers.cpp, demonstrate use of pointers */
#include <cstdlib>
#include <iostream>
using namespace std;
int main() {
int x, *p1, *p2;
x = 1;
p1 = new int;
*p1 = 5;
p2 = new int;
*p2 = 3;
cout << "p1 is " << *p1 << "\np2 is " << *p2 << "\nx is " << x << endl; // Line 15
x = *p2;
p1 = &x;
cout << "p1 is " << *p1 << "\np2 is " << *p2 << "\nx is " << x << endl; // Line 18
return 0;
}
|
Now let's
try to hone our skills at both passing by pointers and
passing by reference and, at the same time, compare these two
methods to the more basic pass by value. You will do this by
modifying the program parameters.cpp. To do so, first
create a new file called parameters.cpp in Emacs and enter
the following code. Then, modify the source to add code to each of the
function stubs (function skeletons) so that they perform the specified functionality. Finally,
enter cout statements and calls to these functions in
main() to test drive each of these functions (follow the
example given for the add function). Finally, compile the
program and ensure the validity of the output.
/* parameters.cpp,
demonstrate calling functions by value, by pointers, and by references */
#include <iostream>
using namespace std;
/* add two numbers together and return the result */
int add(int x, int y) {
// Add Code Here
}
/* take two pointer references to integers and swap their values
i.e. if *x = 5 and *y = 3 before the call to swap then after
*x = 3 and *y = 5 */
void swap(int *x, int *y) {
int temp;
// Add Code Here
}
/* takes one integer by reference and
decrements the value pointed to by x */
void dec(int &x) {
// Add Code Here
}
/* takes one integer by reference and
increments the value pointed to by x */
void inc(int &x) {
// Add Code Here
}
int main() {
int num1, num2;
int result;
cout << "\nEnter two numbers separated by a space: " << endl;
cin >> num1 >> num2;
cout << "\n num1 is " << num1 << ", num2 is " << num2 << endl;
/* Alter and complete the rest of the code to
test the add, swap, decrement, and increment functions and
also display each result */
// result = add(num1, num2);
// cout << "\n result is " << result << endl;
// Add Code Here
}
|
Create a dynamic array and use it directly.
This part of the exercise gave you the opportunity to declare and use a dynamic data array. Now that you've demonstrated your ability to pass pointers and references to functions, let's get into using some dynamic data along with this.
Work in the main() function only here — don't worry about new functions. Create a new file in Emacs called student_scores.cpp and do the following:
|
Array of student scores declared as dynamic data:
Declare a dynamic data array to contain the scores for 10 students. Then, step through the array assigning the counter as the value of the score for that array element. Finally, write another for loop that displays each score. Remember that you have to use the pointer to access the array. You can do do this in three steps:
|
Compile and run this C++ program and ensure the validity of the output.
Hand In: This lab handout with the answers filled in attached
to a listing of your final programs
(use the enscript command from your Programming Style Sheet to print it out:
enscript -E -G -2rj -M Letter -PECT2_PS <filename> or a2ps <filename>).