C語言陣列與指標(Arrays and Pointers)關係解析-差不多是一回事兒

PeaceTree發表於2018-06-10

C語言陣列與指標(Arrays and Pointers)

A pointer to a given data type can store the address of any variable of that particular data type. For example, in the following code, the pointer variable pc stores the address of the character variable c.

char c = 'A';
char *pc = &c;

Here, c is a scalar variable that can store only a single value. 

However, you are already familiar with arrays that can hold multiple values of the same data type in a contiguously allocated memory block. 

So, you might wonder, can we have pointers to arrays too? Indeed, we can.

Let us start with an example code and look at its output. We would discuss its behavior subsequently.

char vowels[] = {'A', 'E', 'I', 'O', 'U'};
char *pvowels = &vowels;
int i;

// Print the addresses
for (i = 0; i < 5; i++) {
    printf("&vowels[%d]: %u, pvowels + %d: %u, vowels + %d: %u\n", i, &vowels[i], i, pvowels + i, i, vowels + i);
}

// Print the values
for (i = 0; i < 5; i++) {
    printf("vowels[%d]: %c, *(pvowels + %d): %c, *(vowels + %d): %c\n", i, vowels[i], i, *(pvowels + i), i, *(vowels + i));
}

A typical output of the above code is shown below

&vowels[0]: 4287605531, pvowels + 0: 4287605531, vowels + 0: 4287605531

&vowels[1]: 4287605532, pvowels + 1: 4287605532, vowels + 1: 4287605532

&vowels[2]: 4287605533, pvowels + 2: 4287605533, vowels + 2: 4287605533

&vowels[3]: 4287605534, pvowels + 3: 4287605534, vowels + 3: 4287605534

&vowels[4]: 4287605535, pvowels + 4: 4287605535, vowels + 4: 4287605535

vowels[0]: A, *(pvowels + 0): A, *(vowels + 0): A

vowels[1]: E, *(pvowels + 1): E, *(vowels + 1): E

vowels[2]: I, *(pvowels + 2): I, *(vowels + 2): I

vowels[3]: O, *(pvowels + 3): O, *(vowels + 3): O

vowels[4]: U, *(pvowels + 4): U, *(vowels + 4): U

&vowels[i] = pvowels + i = vowels + i

vowels[i]  = *(pvowels + i) = *(vowels + i) 

This is because the name of an array itself is a (constant) pointer to the first element of the array. In other words, the notations vowels&vowels[0], and vowels + 0 all point to the same location.


Pointers And Arrays

In C there is a very close connection between pointers and arrays. In fact they are more or less one and the same thing! When you declare an array as:
int a[10];

you are in fact declaring a pointer a to the first element in the array. That is, a is exactly the same as &a[0]. The only difference between a and a pointer variable is that the array name is a constant pointer - you cannot change the location it points at. When you write an expression such as a[i] this is converted into a pointer expression that gives the value of the appropriate element. To be more precise, a[i] is exactly equivalent to *(a+i) i.e. the value pointed at by a + i . In the same way *(a+ 1) is the same as a[1] and so on.

Being able to add one to a pointer to get the next element of an array is a nice idea, but it does raise the question of what it means to add 'one' to a pointer. For example, in most implementations an int takes two memory locations and a float takes four. So if you declare an int array and add one to a pointer to it, then in fact the pointer will move on by two memory locations. However, if you declare a float array and add one to a pointer to it then the pointer has to move on by four memory locations. In other words, adding one to a pointer moves it on by an amount of storage depending on the type it is a pointer to.

This is, of course, precisely why you have to declare the type that the pointer is to point at! Only by knowing that a is a pointer to int and b is a pointer to float can the compiler figure out that

a + 1

means move the pointer on by two memory locations i.e. add 2, and

b + 1

means move the pointer on by four memory locations i.e. add 4. In practice you don't have to worry about how much storage a pointer's base type takes up. All you do need to remember is that pointer arithmetic works in units of the data type that the pointer points at. Notice that you can even use ++ and -- with a pointer, but not with an array name because this is a constant pointer and cannot be changed. So to summarise:

  1. An array's name is a constant pointer to the first element in the array that is a==&a[0] and *a==a[0].
  2. Array indexing is equivalent to pointer arithmetic - that is a+i=&a[i] and *(a+i)==a[i].

It is up to you whether you want to think about an array as an array or an area of storage associated with a constant pointer. The view of it as an array is the more sophisticated and the further away from the underlying way that the machine works. The view as a pointer and pointer arithmetic is more primitive and closer to the hardware. In most cases the distinction is irrelevant and purely a matter of taste.

One final point connected with both arrays and functions is that when you pass an entire array to a function then by default you pass a pointer. This allows you to write functions that process entire arrays without having to pass every single value stored in the array - just a pointer to the first element. However, it also temps you to write some very strange code unless you keep a clear head. Try the following - write a function that will fill an array with random values randdat(a,n) where a is the array and n is its size. Your first attempt might be something like:

void randdat(int *pa , int n)
{
for (pa = 0 ; pa < (n-1) ; pa++ ) *pa = rand()%n + 1;
}

Well I hope your first attempt wouldn't be like this because it is wrong on a number of counts! The problem is that the idea of a pointer and the idea of an index have been confused. The pointer pa is supposed to point to the first element of the array, but the for loop sets it to zero and then increments it though a series of memory locations nowhere near the array. A lesser error is to suppose that n-1 is the correct final value of the array pointer! As before, you will be lucky if this program doesn't crash the system, let alone itself! The correct way of doing the job is to use a for loop to step from 0 to n-1, but to use pointer arithmetic to access the correct array element:

int randdat(int *pa , int n)
{
int i;
for ( i=0 ; i$$$$ n ; ++i)
{
*pa = rand()%n + 1;
++pa;
}}

Notice the way that the for loop looks just like the standard way of stepping through an array. If you want to make it look even more like indexing an array using a for loop you could write:

for(i=0 ; i$$$$n ; ++i) *(pa+i)=rand()%n+1;

or even:

for(i=0 ; i$$$$n ; ++i) pa[i]=rand()%n+1;

In other words, as long as you define pa as a pointer you can use array indexing notation with it and it looks as if you have actually passed an array. You can even declare a pointer variable using the notation:

int pa[];
that is, as an array with no size information. In this way the illusion of passing an array to a function is complete.

相關文章