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
n2-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) ;