#include <iostream>
using namespace std;
int main()
{
int i;
int *i_ptr;
i = 5; // Set the value of i
i_ptr = &i; // Set i_ptr to be the address of i
cout << "i is: " << i << endl;
cout << "&i is: " << &i << endl;
cout << "i_ptr is: " << i_ptr << endl;
cout << "*i_ptr is: " << *i_ptr << endl;
*i_ptr = 25;
cout << endl;
cout << "i is: " << i << endl;
cout << "*i_ptr is: " << *i_ptr << endl;
return 0;
}
|
You'll note, you access the address of i with &i, and you dereference the pointer i_ptr with *i_ptr. Here is the output of this on my (Dr. Plank's) Macintosh (different machines will print pointers differently):
UNIX> g++ pex1.cpp UNIX> g++ -o pex1 pex1.cpp UNIX> pex1 i is: 5 &i is: 0xbffff748 i_ptr is: 0xbffff748 *i_ptr is: 5 i is: 25 *i_ptr is: 25 UNIX>The value of i_ptr is not overly important, except that it is the same as the address of i. Since both i and *i_ptr access the same storage, changing one of them changes the other. This is reflected in the last four lines of the program -- as you can see, when we change *i_ptr, i is changed as well.
#include <iostream>
using namespace std;
void a(int *i_ptr)
{
*i_ptr = 12;
}
int main()
{
int i;
int *i_ptr;
i = 5;
i_ptr = &i;
cout << "Before calling a(): i is: " << i << endl;
a(i_ptr);
cout << "After calling a(): i is: " << i << endl;
return 0;
}
|
Here's it running: you'll see that indeed i is modified by the call to a():
UNIX> g++ -o pex2 pex2.cpp UNIX> pex2 Before calling a(): i is: 5 After calling a(): i is: 12 UNIX>
One of the biggest uses of pointers is to in effect return two things from a function. For example, the function below determines both the square and the cube of a number: (In pex3.cpp),
#include <iostream>
using namespace std;
void square_and_cube(int n, int *n_squared, int *n_cubed)
{
*n_squared = n * n;
*n_cubed = n * n * n;
}
int main()
{
int n, sq, cu;
cout << "Enter n: ";
cin >> n;
square_and_cube(n, &sq, &cu);
cout << n << " squared is " << sq << endl;
cout << n << " cubed is " << cu << endl;
return 0;
}
|
Here's it running: you'll see that indeed i is modified by the call to a( ):
UNIX> g++ -o pex3 pex3.cpp UNIX> pex3 Enter n: 5 5 squared is 25 5 cubed is 125 UNIX>
For example, in the program below, even though the procedure a( ) changes i, that does not modify the variable i in main(). This is because i has been "passed by value." (In pex4.cpp).
#include <iostream>
using namespace std;
void a(int i)
{
i = 7;
cout << "Inside a(). i = " << i << " and &i = " << &i << endl;
}
int main()
{
int i;
i = 5;
cout << "Before calling a(). i = " << i << " and &i = " << &i << endl;
a(i);
cout << "After calling a(). i = " << i << " and &i = " << &i << endl;
return 0;
}
|
Here's it running. I've printed out the addresses of i in both a() and main() -- you can see that they are different. That is because they point to different storage -- i in a() is different from i in main(). That is why changing i in a() does not modify i in main():
UNIX> g++ -o pex4 pex4.cpp UNIX> pex4 Before calling a(). i = 5 and &i = 0xbffff74c Inside a(). i = 7 and &i = 0xbffff730 After calling a(). i = 5 and &i = 0xbffff74c UNIX>
Now, the same thing is true when you pass a pointer as an argument to a procedure -- a copy is made of the pointer. However, when you deference the copy, that will change whatever the pointer points to. For example, look at the following program: (In pex5.cpp).
#include <iostream>
using namespace std;
void a(int *p1, int *p2)
{
cout << endl << "Inside a()" << endl << endl ;
cout << "p1 = " << p1 << " and &p1 = " << &p1 << " and *p1 = " << *p1 << endl;
cout << "p2 = " << p2 << " and &p2 = " << &p2 << " and *p2 = " << *p2 << endl;
p1 = p2;
*p1 = 3;
*p2 = 4;
cout << endl << "Inside a() - after setting p1 to p2" << endl << endl ;
cout << "p1 = " << p1 << " and &p1 = " << &p1 << " and *p1 = " << *p1 << endl;
cout << "p2 = " << p2 << " and &p2 = " << &p2 << " and *p2 = " << *p2 << endl;
}
int main()
{
int i, j, *ptr1, *ptr2;
ptr1 = &i;
ptr2 = &j;
i = 5;
j = 6;
cout << "Before calling a()" << endl << endl ;
cout << "i = " << i << " and &i = " << &i << endl;
cout << "j = " << j << " and &j = " << &j << endl;
cout << endl;
cout << "ptr1 = " << ptr1 << " and &ptr1 = " << &ptr1 << " and *ptr1 = " << *ptr1 << endl;
cout << "ptr2 = " << ptr2 << " and &ptr2 = " << &ptr2 << " and *ptr2 = " << *ptr2 << endl;
a(ptr1, ptr2);
cout << endl << "After calling a()" << endl << endl ;
cout << "i = " << i << " and &i = " << &i << endl;
cout << "j = " << j << " and &j = " << &j << endl;
cout << endl;
cout << "ptr1 = " << ptr1 << " and &ptr1 = " << &ptr1 << " and *ptr1 = " << *ptr1 << endl;
cout << "ptr2 = " << ptr2 << " and &ptr2 = " << &ptr2 << " and *ptr2 = " << *ptr2 << endl;
return 0;
}
|
Here's it running. Study this carefully:
UNIX> g++ -o pex5 pex5.cpp UNIX> pex5 Before calling a() i = 5 and &i = 0xbffff73c j = 6 and &j = 0xbffff738 ptr1 = 0xbffff73c and &ptr1 = 0xbffff734 and *ptr1 = 5 ptr2 = 0xbffff738 and &ptr2 = 0xbffff730 and *ptr2 = 6 Inside a() p1 = 0xbffff73c and &p1 = 0xbffff720 and *p1 = 5 p2 = 0xbffff738 and &p2 = 0xbffff724 and *p2 = 6 Inside a() - after setting p1 to p2 p1 = 0xbffff738 and &p1 = 0xbffff720 and *p1 = 4 p2 = 0xbffff738 and &p2 = 0xbffff724 and *p2 = 4 After calling a() i = 5 and &i = 0xbffff73c j = 4 and &j = 0xbffff738 ptr1 = 0xbffff73c and &ptr1 = 0xbffff734 and *ptr1 = 5 ptr2 = 0xbffff738 and &ptr2 = 0xbffff730 and *ptr2 = 4 UNIX>
Ok -- ptr1 equals the address of i and ptr2 equals the address of j. When we pass them to a(), copies are made -- p1 is set to ptr1 and p2 is set to ptr2. This is reflected in the output in the beginning of a(), where both ptr1 and p1 equal 0xbffff73c -- the address of i. You'll note, though, that ptr1 and p1 have different addresses (0xbffff734 vs. 0xbffff720). That is because p1 is a copy of ptr1
When we modify p1 by setting it equal to p2, you'll see that both of them have values of 0xbffff738 -- the address of j. So they both modify j by setting it to three, then 4. When a() finishes, i is unchanged, but j is 4. Moreover, you'll notice that ptr1 had not been modified by the call to a(). That is because arguments are passed by value -- even pointers!
#include <iostream>
using namespace std;
int main()
{
int array[10];
int *a_ptr;
a_ptr = array;
array[0] = 15;
cout << "array[0]: " << array[0] << endl;
cout << "*a_ptr: " << *a_ptr << endl;
return 0;
}
|
When it runs, you can see that dereferencing a_ptr is the same as accessing a[0].
UNIX> g++ -o pex6 pex6.cpp UNIX> pex6 array[0]: 15 *a_ptr: 15 UNIX>
In fact, you can use standard array accessing methods (square brackets) on the pointer, just like you do with an array. You can see this in pex7.cpp:
#include <iostream>
using namespace std;
int main()
{
int array[10];
int *a_ptr;
int i;
a_ptr = array;
for (i = 0; i < 10; i++) a_ptr[i] = 30 + i;
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - array[" << i << "]: " << array[i]
<< " - a_ptr[" << i << "]: " << a_ptr[i] << endl;
}
return 0;
}
|
As you can see, you can treat a_ptr just like array.
UNIX> g++ -o pex7 pex7.cpp UNIX> pex7 i: 0 - array[0]: 30 - a_ptr[0]: 30 i: 1 - array[1]: 31 - a_ptr[1]: 31 i: 2 - array[2]: 32 - a_ptr[2]: 32 i: 3 - array[3]: 33 - a_ptr[3]: 33 i: 4 - array[4]: 34 - a_ptr[4]: 34 i: 5 - array[5]: 35 - a_ptr[5]: 35 i: 6 - array[6]: 36 - a_ptr[6]: 36 i: 7 - array[7]: 37 - a_ptr[7]: 37 i: 8 - array[8]: 38 - a_ptr[8]: 38 i: 9 - array[9]: 39 - a_ptr[9]: 39 UNIX>
The reason why this is important is that when you pass an array to a function as an argument, you are passing a pointer to its first element. This means that if you change the contents of the array inside the function, that will change the contents of the original array. This is because only the pointer to the contents is copied in the function call. It is important to understand this.
So look at pex8.cpp:
#include <iostream>
using namespace std;
void initialize_array(int *a)
{
int i;
for (i = 0; i < 10; i++) a[i] = 40+i;
}
void display_array(int *a)
{
int *p; //traveling pointer
cout << "Inside display_array: ";
for (p = a; p < a + 10; p++)
cout << *p << " ";
cout << endl << endl;
}
int main()
{
int array[10];
int i;
initialize_array(array);
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - array[" << i << "]: "
<< array[i] << endl;
}
return 0;
}
|
When you run it, you'll see that initialize_array() changes the contents of array. That is because a is a pointer to the first element of array. Also, study the pointer arithmetic in display_array().
UNIX> g++ -o pex8 pex8.cpp UNIX> pex8 Inside display_array: 40 41 42 43 44 45 46 47 48 49 i: 0 - array[0]: 40 i: 1 - array[1]: 41 i: 2 - array[2]: 42 i: 3 - array[3]: 43 i: 4 - array[4]: 44 i: 5 - array[5]: 45 i: 6 - array[6]: 46 i: 7 - array[7]: 47 i: 8 - array[8]: 48 i: 9 - array[9]: 49 UNIX>
#include <iostream>
#include <vector>
using namespace std;
vector <int> change_and_return(vector <int> a)
{
int i;
for (i = 0; i < a.size(); i++) a[i] = 30 + i;
return a;
}
int main()
{
vector <int> a(10), b;
int i;
for (i = 0; i < a.size(); i++) a[i] = 10+i;
b = change_and_return(a);
for (i = 0; i < a.size(); i++) {
cout << "i: " << i << " - a[" << i << "]: " << a[i] <<
" - b[" << i << "]: " << b[i] << endl;
}
}
|
When you run it, you'll see that change_and_return() changes the contents of a, but that its version of a is a copy of the version in main(). So the version of a in main() remains unchanged when we print it out. Note also that when we return a vector, it makes a copy. So this program actually has three copies of 10-element vectors -- the original a in main, the copy of a in change_and_return(), and the return value that is put into b.
UNIX> g++ -o pex9 pex9.cpp UNIX> pex9 i: 0 - a[0]: 10 - b[0]: 30 i: 1 - a[1]: 11 - b[1]: 31 i: 2 - a[2]: 12 - b[2]: 32 i: 3 - a[3]: 13 - b[3]: 33 i: 4 - a[4]: 14 - b[4]: 34 i: 5 - a[5]: 15 - b[5]: 35 i: 6 - a[6]: 16 - b[6]: 36 i: 7 - a[7]: 17 - b[7]: 37 i: 8 - a[8]: 18 - b[8]: 38 i: 9 - a[9]: 19 - b[9]: 39 UNIX>
This makes vectors convenient, because you can return them from procedures and you don't have to worry about overwriting stuff. However, it can be inefficient time-wise -- making copies of large vectors will consume time.
Now, look at the array version of the above code. In: pexA.cpp:
#include <iostream>
using namespace std;
int *change_and_return(int *a)
{
int i;
for (i = 0; i < 10; i++) a[i] = 30 + i;
return a;
}
int main()
{
int a[10], *b;
int i;
for (i = 0; i < 10; i++) a[i] = 10+i;
b = change_and_return(a);
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - a[" << i << "]: " << a[i] <<
" - b[" << i << "]: " << b[i] << endl;
}
}
|
When you run it, you'll see that change_and_return() actually changes a in main(). That's because there is only one copy of the array -- the procedure call and return value simply pass around pointers:
UNIX> g++ -o pexA pexA.cpp UNIX> pexA i: 0 - a[0]: 30 - b[0]: 30 i: 1 - a[1]: 31 - b[1]: 31 i: 2 - a[2]: 32 - b[2]: 32 i: 3 - a[3]: 33 - b[3]: 33 i: 4 - a[4]: 34 - b[4]: 34 i: 5 - a[5]: 35 - b[5]: 35 i: 6 - a[6]: 36 - b[6]: 36 i: 7 - a[7]: 37 - b[7]: 37 i: 8 - a[8]: 38 - b[8]: 38 i: 9 - a[9]: 39 - b[9]: 39 UNIX> UNIX>
#include <iostream>
using namespace std;
int *initialize_array()
{
int a[10];
int *a_ptr;
int i;
for (i = 0; i < 10; i++) a[i] = 30+i;
a_ptr = a;
return a_ptr;
}
int main()
{
int *a;
int i;
a = initialize_array();
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - a[" << i << "]: " << a[i] << endl;
}
}
|
What it does is have
Well, because the memory for a in
initialize_array() only exists while
initialize_array() is running. When
initialize_array() returns, the memory is freed up to be reused.
And it does get reused in the cout statement.
This is a disastrous bug because it is hard to track down.
Remember it, because it will happen to you someday.
The vector version of this code works fine -
pexC.cpp:
This works fine because a copy of a is made and returned
to main().
This creates four strings -- note, the middle two have to specify the memory;
the first and last are simply pointers. If you use c_str() the pointer
must be a const, which says that you cannot modify its contents.
The program also shows the use of strlen(), which returns the length of
the string, minus the NULL character. Then it shows strcmp() which compares
two strings lexicographically, and returns 0 if they are equal, a negative number
if the first is less than the second, and a positive number if the first is
greater than the second. Lexicographic comparison is done using the ASCII character
codes for the characters. Since space is less than capital-J, the fourth strcmp()
statement shows that " Jim" is less than "Jim".
Note that although it appears that strcmp() is returning the difference between
the ASCII character codes of the differing characters, you should not rely on that, because
the definition of strcmp() only specifies positive/negative numbers. This means
that on a different machine, it may return different positive/negative values.
If they find what they're looking for, they return a pointer to it inside s.
If they don't, they return the global constant NULL.
Below is a nice example of it working
(pexE.cpp):
This shows calling the various functions on a user-entered string:
UNIX> g++ -o pexB pexB.cpp
UNIX> pexB
i: 0 - a[0]: 30
i: 1 - a[1]: 10
i: 2 - a[2]: 1
i: 3 - a[3]: 11597
i: 4 - a[4]: -1599039200
i: 5 - a[5]: -1599040224
i: 6 - a[6]: -1073744104
i: 7 - a[7]: -1867372625
i: 8 - a[8]: -1610605544
i: 9 - a[9]: 10
UNIX>
#include <iostream>
#include <vector>
using namespace std;
vector <int> initialize_array()
{
vector <int> a(10);
int i;
for (i = 0; i < 10; i++) a[i] = 30+i;
return a;
}
int main()
{
vector <int> a;
int i;
a = initialize_array();
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - a[" << i << "]: " << a[i] << endl;
}
}
UNIX> g++ -o pexC pexC.cpp
UNIX> pexC
i: 0 - a[0]: 30
i: 1 - a[1]: 31
i: 2 - a[2]: 32
i: 3 - a[3]: 33
i: 4 - a[4]: 34
i: 5 - a[5]: 35
i: 6 - a[6]: 36
i: 7 - a[7]: 37
i: 8 - a[8]: 38
i: 9 - a[9]: 39
UNIX>
8. C-style strings, char *'s and c_str
C-style strings are character arrays that are NULL-terminated.
This means that their last character is '\0', which is
called the "NULL character." You can create a C-style string
in many ways. The program
pexD.cpp
uses four ways -- using string constants (in double-quotes),
using array initialization, writing code to create a string,
and finally using the c_str() method of a C++ string.
Note, they all need the NULL character at the end:
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
char *s1 = "Jim";
char s2[6] = { 'P', 'l', 'a', 'n', 'k', '\0' };
char s3[4];
const char *s4;
string string4 = "Mayo";
s3[0] = 'M';
s3[1] = 's';
s3[2] = '.';
s3[3] = '\0';
s4 = string4.c_str();
cout << "The four strings: " << s1 << ", ";
cout << s2 << ", " << s3 << ", " << s4 << endl;
cout << endl;
cout << "Strlen(s1) = " << strlen(s1);
cout << ". Strlen(s2) = " << strlen(s2);
cout << ". Strlen(s3) = " << strlen(s3);
cout << ". Strlen(s4) = " << strlen(s4) << "." << endl;
cout << endl;
cout << "Strcmp(s1, \"Jim\") = " << strcmp(s1, "Jim") << endl;
cout << "Strcmp(s1, \"Fred\") = " << strcmp(s1, "Fred") << endl;
cout << "Strcmp(s1, \"Plank\") = " << strcmp(s1, "Plank") << endl;
cout << "Strcmp(s1, \" Jim\") = " << strcmp(s1, " Jim") << endl;
cout << "Strcmp(s1, \"jim\") = " << strcmp(s1, "jim") << endl;
cout << "Strcmp(s1, \"Jix\") = " << strcmp(s1, "Jix") << endl;
return 0;
}
UNIX> g++ -o pexD pexD.cpp
UNIX> pexD
The four strings: Jim, Plank, Ms., Mayo
Strlen(s1) = 3. Strlen(s2) = 5. Strlen(s3) = 3. Strlen(s4) = 4.
Strcmp(s1, "Jim") = 0
Strcmp(s1, "Fred") = 4
Strcmp(s1, "Plank") = -6
Strcmp(s1, " Jim") = 42
Strcmp(s1, "jim") = -32
Strcmp(s1, "Jix") = -11
UNIX>
9. strchr(), strrchr() and strstr()
These are very useful functions on strings. Here are the prototypes:
char *strchr(char *s, char c);
char *strrchr(char *s, char c);
char *strstr(char *s, char *tofind);
They all find something in the string s -- strchr() finds the
first occurrence of c, and strrchr() finds the last occurrence
of c. strstr() finds the first occurrence of the substring tofind.
#include <iostream>
#include <cstring>
using namespace std;
int main()
{
const char *s;
string str;
char *found;
cout << "Enter a string: ";
cin >> str;
s = str.c_str();
found = strchr(s, 'a');
cout << "strchr(\"" << s << "\", 'a') returned ";
if (found == NULL) { // -OR- if (!found) ...
cout << "NULL\n";
} else {
cout << '"' << found << '"' << endl;
}
found = strrchr(s, 'a');
cout << "strrchr(\"" << s << "\", 'a') returned ";
if (!found) {
cout << "NULL\n";
} else {
cout << '"' << found << '"' << endl;
}
found = strstr(s, "ba");
cout << "strstr(\"" << s << "\", \"ba\") returned ";
if (found == NULL) {
cout << "NULL\n";
} else {
cout << '"' << found << '"' << endl;
}
return 0;
}
UNIX> pexE
Enter a string: Jim
strchr("Jim", 'a') returned NULL
strrchr("Jim", 'a') returned NULL
strstr("Jim", "ba") returned NULL
UNIX> pexE
Enter a string: Abacab
strchr("Abacab", 'a') returned "acab"
strrchr("Abacab", 'a') returned "ab"
strstr("Abacab", "ba") returned "bacab"
UNIX>
10. Sizeof and const -- read the book