int V[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 } ; double X[100] = { 1.0e15, 3.7, 20 } ; int squares[] = { 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 } ;
When an array is declared with const
, its
elements cannot be changed.
const int DaysOfMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } ;
int D2[5][3] ; double D3[2][4][6] ;
If X
is declared with int X[5][3] ;
it is an array of 5 elements that are arrays of 3 elements
that are integers.
X
: array of arrays of integersX[3]
: array of 3 integersX[3][2]
: integer
Consequently, arrays are stored in
row-major order.
So, the first seven elements of X
, as stored in memory, are
X[0][0]
, X[0][1]
, X[0][2]
,
X[1][0]
, X[1][1]
, X[1][2]
,
and X[2][0]
.
int A[3][2] = { { 2, 4}, {3, 9}, {4, 16 } } ; int B[3][2] = { 2, 4, 3, 9, 4, 16 } ;
double M[1000][1000], i, j ; for (i=0; i<1000; ++i) { for (j=0; j<1000; ++j) { if (i==j) { M[i][j] = 1.0 ; } else { M[i][j] = 0.0 ; } } }
The array variable itself is a constant, but you can change its elements.
If V
is a one-dimensional array,
assigning to V
is not allowed but
assigning to V[5]
is allowed.
If X
is a two-dimensional array,
assigning to X[5]
is not allowed but
assigning to X[5][5]
is allowed.
C treats an array like a constant reference to
its 0'th elements; that is,
V
and &V[0]
are the same.
For a two-dimensional array X
,
X[5]
and &X[5][0]
are the same.
The postfix array index has a higher precedence
that the prefix address operator.
Therefore,
&X[5][0]
is &((X[5])[0])
.
void reverse(int V[10]) { int i, j ; for (i=0, j=9; i<j; ++i, ++j) { int t ; t = V[i] ; V[i] = V[j] ; V[j] = t ; } }
#define SIZE 209 void reverse(int V[SIZE]) { int i, j ; for (i=0, j=SIZE-1; i<j; ++i, ++j) { int t ; t = V[i] ; V[i] = V[j] ; V[j] = t ; } }
void reverse(int V[], int n) { int i, j ; for (i=0, j=n-1; i<j; ++i, ++j) { int t ; t = V[i] ; V[i] = V[j] ; V[j] = t ; } }
Let's try a generic identity
function.
identity(int X[][], int n) ;
This is not allowed! Suppose identity
was called with
two different array sizes.
int Y[300][300] ; int Z[400][400] ;
Y[1][0]
is 300 integers past the start of Y
.
Z[1][0]
is 400 integers past the start of Z
.
Since the compiler can't "see" the calls, it doesn't know what code to
compile.
C99 provides for variable-length arrays or VLAs. The dimension of arrays can be set from expressions, though these dimensions cannot be changed once the array is declared.
int numStudents = 31 ; char namesStudents[numStudents][16] ;
Variable length arrays can also be passed as parameters.
identity(int n, int X[n][n]) ;
ANSI C allows the safe passing of arrays if all but the first dimension is fixed.
identity(int X[][100]) ;
int R[3][5] ; |
R is a 3×5 array |
R[0][0] |
int *S[5] ; |
S is an array of 5 pointers to integers |
*S[0] |
int (*T)[5] ; |
T is a pointer to an array of 5 integers |
(*T)[0] |
int **V ; |
V is a pointer to a pointer to an integer |
**V |
#include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) { int R[3][5] ; int *S[5] ; int (*T)[5] ; int **V ; for(int i=0; i<3; ++i) { for (int j=0; j<5; ++j) { R[i][j] = i*10 + j ; } } for(int i=0; i<5; ++i) { S[i] = &R[i%3][i] ; } T = &R[1] ; V = &S[2] ; puts("\nValues of R[i][j]") ; for(int i=0; i<3; ++i) { for (int j=0; j<5; ++j) { printf(" R[%2d,%2d]: %4d\n", i, j, R[i][j]) ; } } puts("\nValues of S[i]") ; for(int i=0; i<5; ++i) { printf(" S[%2d]: %08x\n", i, (unsigned int)S[i]) ; } puts("Values of *S[i]") ; for(int i=0; i<5; ++i) { printf(" *S[%2d]: %4d\n", i, *S[i]) ; } puts("\nValue of T") ; printf(" T: %08x\n", (unsigned int)T) ; puts("Values of (*T)[i]") ; for(int i=0; i<5; ++i) { printf(" (*T)[%2d]: %4d\n", i, (*T)[i]) ; } puts("\nValue of V") ; printf(" V: %08x\n", (unsigned int)V) ; puts("Value of *V") ; printf(" *V: %08x\n", (unsigned int)*V) ; puts("Value of **V") ; printf(" **V: %4d\n", **V) ; return (EXIT_SUCCESS) ; }
Values of R[i][j] R[ 0, 0]: 0 R[ 0, 1]: 1 R[ 0, 2]: 2 R[ 0, 3]: 3 R[ 0, 4]: 4 R[ 1, 0]: 10 R[ 1, 1]: 11 R[ 1, 2]: 12 R[ 1, 3]: 13 R[ 1, 4]: 14 R[ 2, 0]: 20 R[ 2, 1]: 21 R[ 2, 2]: 22 R[ 2, 3]: 23 R[ 2, 4]: 24 Values of S[i] S[ 0]: bf8ebcc8 S[ 1]: bf8ebce0 S[ 2]: bf8ebcf8 S[ 3]: bf8ebcd4 S[ 4]: bf8ebcec Values of *S[i] *S[ 0]: 0 *S[ 1]: 11 *S[ 2]: 22 *S[ 3]: 3 *S[ 4]: 14 Value of T T: bf8ebcdc Values of (*T)[i] (*T)[ 0]: 10 (*T)[ 1]: 11 (*T)[ 2]: 12 (*T)[ 3]: 13 (*T)[ 4]: 14 Value of V V: bf8ebcbc Value of *V *V: bf8ebcf8 Value of **V **V: 22
If p
is an integer pointer (which could just be the
first element of an array), what is p[i]
?
If p
is an integer pointer and i
is an integer,
p+i
is the address of the i
'th integer
after the one p
is addressing.
So p[i]
is *(p+i)
.
reverse(int *p, int *q) { if (p>q) { int *t ; t = p ; p = q ; q = t ; } while (p<q) { int t ; t = *p ; *p = *q ; *q = t ; ++p ; --q ; } }
It is unlikely that p++
adds one to the bits stored in
p
. It adds the size of an integer.
int *p ; double *q ;
q = p ; |
illegal |
*q = *p ; |
allowed, *p is made into a double |
q = (double *)p ; |
allowed, but probably not a good idea |
Let's try to get identity
to work in a unsavory manner.
Just pass in the address of the first element of the two-dimensional
array along with the size of its dimensions.
identity(&X[0][0], n) ;
Now do some really ugly pointer manipulation.
Remember that every n+1
elements must be set equal to 1.
Also n+1
×n-1
is
n
2-1
.
identity(int *p, int n) { int i, j ; for (i=0; i<n-1; ++i) { *p = 1 ; ++p ; for (j=0; j<n; ++j) { *p = 0 ; ++p ; } } *p = 1 ; }
Or even slightly more obscurely.
identity(int *p, int n) { int i, j ; for (i=0; i<n-1; ++i) { *(p++) = 1 ; for (j=0; j<n; ++j) *(p++) = 0 ; } *p = 1 ; }
If a function does not modify an array, this should be clearly indicated in the prototype.
int sum(const int a[], int n) ; double DotProduct(const double *X, const double *Y, int n) ;
int * * const R ; |
Prevents assignment to R |
int * const * R ; |
Prevents assignment to *R |
const int * * R ; |
Prevents assignment to **R |
Take a look at that example on pages 383 of the textbook (C Primer Plus by Stephen Prata).
int * p1; const int * p2 ; const int ** pp2 ; p1 = p2 ; /* GCC warning: Assignment discard qualifiers */ p2 = p1 ; pp2 = &p1 ; /* GCC warning: Assignment from incompatiable type */
The first assignment is forbidden because it would allow the
*p2
to be modified as *p1
.
The second assignment is allowed because the original p2
is "lost". Assigning to *p1
cannot modify
the original *p2
values.
The third assignment is also forbidden. So, why is assigning
a int *
to a const int *
allowed, but assigning a int **
to a
const int **
not allowed?
The reason is seen in this modified example.
int * p1; const int ** pp2 ; const int n = 13 ; pp2 = &p1 ; /* GCC warning: Assignment from incompatiable type */ *pp2 = &n ; *p1 = 10 ;
If the assignment were allowed, the constant n
could be
modified.
Rather than attempt to understand these complex rules, it might
be better to never to assign to anything with a const
qualifier anywhere in its declaration.
The following looks like a useful function.
Can it ever be used
to modify its const
arguments.
void ArraySum(int *A, const int *B, const int *C, int n) { int i ; for (i=0; i<n; ++i) { A[i] = B[i] + C[i] ; } }
It could, if the arrays overlap.
int X[20] ; X[0] = 1 ; ArraySum(&X[1], X, X, 19) ;
This possibility of aliasing prevents the compiler from performing several additions of the loop concurrently.
In C99 (and C++) declaring a pointer with the restrict
qualifier informs the compiler that an object is allowed to have
only one name. If ArraySum
were declared with
restricted pointers, the result of the aliased execution of
ArraySum
would be undefined.
Often array sizes depend on constants.
One file may define the constants.
#define NUMCARS 35
Declarations will use the constants to set aside memory for data.
double mpg[NUMCARS] ; int wheels[NUMCARS] ;
Loops will also use the constants to do the work.
double avgMPG ; avgMPG = 0.0 ; for (i=0; i<NUMCARS; ++i) { avgMPG += mpg[i] ; } avgMPG = avgMPG/N ;
However don't be surprised to see obscure uses of
sizeof
to determine the number of elements within an array.
const int elementsInV = sizeof(V)/sizeof(V[0]) ;
// generate a histogram from an array argument void genHistogram(const int V[], int vSize, int H[], int hSize) ; // add the elements of an array int sumElements(const int V[], int vSize) ; // find the largest element in an array int maxElement(const int V[], int vSize) ; // count the number times a value occurs within an array) int countValue(const int V[], int vSize, int e) ;