int V[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 } ; double X[100] = { 1.0e15, 3.7, 20 } ;
When an array is declared with const
, its
elements cannot be changed.
const DaysOfMonth = { 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 three 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 alloweed.
If X
is a two-dimensional array,
assigning to X[5]
is not allowed but
assigning to X[5][5]
is allowed.
C treats are array like a constant reference to
its 0'th elements; that is,
V
and &V[0]
are the same.
For a two-dimensional 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.
identity(int n, int X[n][n]) ;
ANSI C allows the safe passing if 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 U[][5] ; |
U is a pointer to an array of 5 integers |
U[0][0] |
int **V ; |
V is a pointer to a pointer to an integer |
**V |
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, but 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) ;