|
Pointers and arrays
The identifier of an array is unlike the
identifiers for ordinary variables. For one thing, an array identifier is not an
lvalue; you cannot assign to it. It’s really just a hook into the
square-bracket syntax, and when you give the name of an array, without square
brackets, what you get is the starting address of the array:
//: C03:ArrayIdentifier.cpp
#include <iostream>
using namespace std;
int main() {
int a[10];
cout << "a = " << a << endl;
cout << "&a[0] =" << &a[0] << endl;
} ///:~
When you run this program you’ll
see that the two addresses (which will be printed in hexadecimal, since there is
no cast to long) are the same.
So one way to look at the array
identifier is as a read-only pointer to the beginning of an array. And although
we can’t change the array identifier to point somewhere else, we
can create another pointer and use that to move around in the array. In
fact, the square-bracket syntax works with regular
pointers as well:
//: C03:PointersAndBrackets.cpp
int main() {
int a[10];
int* ip = a;
for(int i = 0; i < 10; i++)
ip[i] = i * 10;
} ///:~
The fact that naming an array produces
its starting address turns out to be quite important when you want to pass an
array to a function. If you declare an array as a function argument, what
you’re really declaring is a pointer. So in the following example,
func1( ) and func2( ) effectively have the same argument
lists:
//: C03:ArrayArguments.cpp
#include <iostream>
#include <string>
using namespace std;
void func1(int a[], int size) {
for(int i = 0; i < size; i++)
a[i] = i * i - i;
}
void func2(int* a, int size) {
for(int i = 0; i < size; i++)
a[i] = i * i + i;
}
void print(int a[], string name, int size) {
for(int i = 0; i < size; i++)
cout << name << "[" << i << "] = "
<< a[i] << endl;
}
int main() {
int a[5], b[5];
// Probably garbage values:
print(a, "a", 5);
print(b, "b", 5);
// Initialize the arrays:
func1(a, 5);
func1(b, 5);
print(a, "a", 5);
print(b, "b", 5);
// Notice the arrays are always modified:
func2(a, 5);
func2(b, 5);
print(a, "a", 5);
print(b, "b", 5);
} ///:~
Even though func1( ) and
func2( ) declare their arguments differently, the usage is the same
inside the function. There are some other issues that this example reveals:
arrays cannot be passed by
value[32], that is,
you never automatically get a local copy of the array
that you pass into a function. Thus, when you modify an array, you’re
always modifying the outside object. This can be a bit confusing at first, if
you’re expecting the pass-by-value provided with ordinary
arguments.
You’ll notice that
print( ) uses the square-bracket syntax for array arguments. Even
though the pointer syntax and the square-bracket syntax are effectively the same
when passing arrays as arguments, the square-bracket syntax makes it clearer to
the reader that you mean for this argument to be an array.
Also note that the size argument
is passed in each case. Just passing the address of an array isn’t enough
information; you must always be able to know how big the array is inside your
function, so you don’t run off the end of that array.
Arrays can be of any type, including
arrays of pointers. In fact, when
you want to pass command-line arguments into your program, C and C++ have a
special argument list for main( ), which looks like
this:
int main(int argc, char* argv[]) { // ...
The first argument is the number of
elements in the array, which is the second argument. The second argument is
always an array of char*, because the arguments are passed from the
command line as character arrays (and remember, an array can be passed only as a
pointer). Each whitespace-delimited cluster of characters on the command line is
turned into a separate array argument. The following program prints out all its
command-line arguments by stepping through the array:
//: C03:CommandLineArgs.cpp
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
cout << "argc = " << argc << endl;
for(int i = 0; i < argc; i++)
cout << "argv[" << i << "] = "
<< argv[i] << endl;
} ///:~
You’ll notice that argv[0]
is the path and name of the program itself. This allows the program to discover
information about itself. It also adds one more to the array of program
arguments, so a common error when fetching
command-line arguments is to grab
argv[0] when you want argv[1].
You are not forced to use
argc and argv as
identifiers in main( ); those identifiers are only conventions (but
it will confuse people if you don’t use them). Also, there is an alternate
way to declare argv:
int main(int argc, char** argv) { // ...
Both forms are equivalent, but I find the
version used in this book to be the most intuitive when reading the code, since
it says, directly, “This is an array of character
pointers.”
All you get from the command-line is
character arrays; if you want to treat an argument as some other type, you are
responsible for converting it inside your program. To facilitate the
conversion to numbers, there are
some helper functions in the Standard C library, declared in
<cstdlib>. The
simplest ones to use are atoi( ),
atol( ), and
atof( ) to convert an ASCII character array
to an int, long, and double floating-point value,
respectively. Here’s an example using atoi( ) (the other two
functions are called the same way):
//: C03:ArgsToInts.cpp
// Converting command-line arguments to ints
#include <iostream>
#include <cstdlib>
using namespace std;
int main(int argc, char* argv[]) {
for(int i = 1; i < argc; i++)
cout << atoi(argv[i]) << endl;
} ///:~
In this program, you can put any number
of arguments on the command line. You’ll notice that the for loop
starts at the value 1 to skip over the program name at argv[0].
Also, if you put a floating-point number containing a decimal point on the
command line, atoi( ) takes only the digits up to the decimal point.
If you put non-numbers on the command line, these come back from
atoi( ) as zero.
|
|