Arrays
An array is a contiguous sequence of same-type values in memory. For example,
int five_primes = { 2, 3, 5, 7, 123457 };
appears in (32-bit, little-endian) memory as:
[ 02 00 00 00 ][ 03 00 00 00 ][ 05 00 00 00 ][ 07 00 00 00 ][ 41 E1 01 00 ] |
An array has three pieces of information associated with it:
- the
address of the first element
- the number of elements in the array
- the type of the elements in the array (which includes the size in bytes of each element in the array)
An array will degrade to a pointer at the earliest opportunity. That's why you can use an array's name like a pointer.
1 2 3 4 5 6 7
|
#include <cstring>
int main()
{
char s[] = "Hello world!"; // s is an array
int length = std::strlen( s ); // but strlen() takes a pointer as argument
...
|
You can even make a direct assignment:
1 2
|
char s[] = "Hola mundo";
char* p = s; // s automatically converts to a pointer, so this assignment works okay!!!
|
Pointers
In C and C++, pointer arithmetic is important, and there are a lot of ways to write it. Specifically, the [] operator takes a
pointer and an integer as arguments. It then calculates a
new pointer. The following are equivalent ways to access the fourth element of an array:
a[3] *(a+3) 3[a]
(That last one there is an oddity with C and C++. Only weirdos doing weird stuff ever use it. So you can safely ignore it.)
At this point you should be able to see that you can access any element of an array if all you have is a
pointer to the first element in the array.
This helps us answer your last question:
Arguments
Function arguments may be pointers, and a pointer is always to a specific
type of thing. In the case of what looks to be an array:
int foo( int a[20] )
it turns out that C and C++ don't actually care how many elements are in the array. You could have just written:
int foo( int a[] )
The reason is because that is not an array you are passing as argument to the function, but a
pointer. That's right, you could have just written:
int foo( int* a )
The argument
a is a pointer to an integer. And, using regular pointer arithmetic, you can access any element of the array. This is why, BTW, it is so important to pass the length of your array to a function that takes one -- because the function has no idea how many elements are in your array. The actual number of elements in the argument array could be different each time:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#include <iostream>
void myfn( int* a, int n )
{
for (int i = 0; i < n; i++)
std::cout << i << ": " << a[i] << "\n";
}
int main()
{
int xs[] = { 1, 2, 3 };
int ys[] = { 9, 8, 7, 6, 5 };
myfn( xs, 3 );
myfn( ys, 5 );
}
|
Multidimensional
An array with multiple dimensions is really an array of arrays. For example:
int two_dims[3][4];
declares an array of three elements. Each element is itself an array of four elements. Let's initialize the array to get a better view:
1 2 3 4 5 6
|
int two_dims[3][4] =
{
{ 11, 12, 13, 14 },
{ 21, 22, 23, 24 },
{ 31, 32, 33, 34 }
};
|
Three elements. Each element is an array of four integers. We can rewrite it this way as well:
1 2 3
|
typedef int element[4]; // an element is an array of four integers
element two_dims[3]; // two_dims is an array of three elements
|
Now, when you wish to pass such a thing as an argument, remember that you get a pointer to the first element of the array. The type of the entire first element is important, but not the number of elements in the array.
Hence, you could write your function as:
void foo( element* two_dims )
or
void foo( element two_dims[] )
or
void foo( element two_dims[3] )
or even
void foo( element two_dims[3000] )
...
It all winds up meaning the same thing. And if you don't have a typedef handy for the type of the elements in the array, you must write it out as well:
void foo( int two_dims[][4] )
See how the type (
int[4]) is added to the argument (
two_dims[])? Does that make sense?
Static vs Dynamic Memory
When you declare an array (just like in the examples above), that is over memory that is part of your program. It is accessible in the same place before and during the entire time your program runs. This is important, because the compiler knows exactly where that memory will be when it builds your program.
But there is nothing saying that all the memory your program accesses must be like that.
The
new and
delete operators (they're really like functions) ask for
additional memory to be given to or taken from your program. Neither you nor the compiler know where that memory is going to be, so you need a pointer to keep track of it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
int* a;
a = new int[20]; // get me the address of the first integer in an array of 20 integers.
a[3] = 12; // do stuff with the array
delete [] a; // forget all about the array
a = new int; // now just get me the address of a single integer
*a = 92; // do stuff with that integer
a[0] += 1;
delete a; // forget all about that integer
|
Hope this helps.
Edit: fixed typo