书籍:C Primer Plus第六版英文版
An array is composed of a series of elements of one data type.
int powers[8] = {1,2,4,6,8,16,32,64}; /* ANSI C and later */
As you can see, you initialize an array by using a comma-separated list of values enclosed in braces. You can use spaces between the values and the commas, if you want.
Sometimes you might use an array that’s intended to be a read-only array. That is, the program will retrieve values from the array, but it won’t try to write new values into the array. In such cases, you can, and should, use the const keyword when you declare and initialize the array.
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
C99 added a new capability: designated initializers. This feature allows you to pick and choose which elements are initialized.
int days[MONTHS] = {31,28, [4] = 31,30,31, [1] = 29};
First, if the code follows a designated initializer with further values, as in the sequence [4] = 31,30,31, these further values are used to initialize the subsequent elements. That is, after initializing days[4] to 31, the code initializes days[5] and days[6] to 30 and 31, respectively.
Second, if the code initializes a particular element to a value more than once, the last initialization is the one that takes effect.
C doesn’t let you assign one array to another as a unit. Nor can you use the list-in-braces form except when initializing.
int oxen[SIZE] = {5,3,2,8}; /* ok here */
int yaks[SIZE];
yaks = oxen; /* not allowed */
yaks[SIZE] = oxen[SIZE]; /* out of range */
yaks[SIZE] = {5,3,2,8}; /* doesn't work */
The compiler doesn’t check to see whether the indices are valid. The result of using a bad index is, in the language of the C standard, undefined. That means when you run the program, it might seem to work, it might work oddly, or it might abort.
const float rain[YEARS][MONTHS] =
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,5.2}
};
An array name is also the address of the first element of
the array.
That is, if flizny is an array, the following is true:
flizny == &flizny[0]; // name of array is the address of the first element
Both are constants because they remain fixed for the duration of the program.
dates + 2 == &date[2] // same address
*(dates + 2) == dates[2] // same value
/* day_mon3.c -- uses pointer notation */
#include
#define MONTHS 12
int main(void)
{
int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
int index;
for (index = 0; index < MONTHS; index++)
printf("Month %2d has %d days.\n", index +1,
*(days + index)); // same as days[index]
return 0;
}
Here, days is the address of the first element of the array, days + index is the address of element days[index], and *(days + index) is the value of that element, just as days[index] is.
int * ar:
int sum (int ar[], int n);
The form int * ar always means that ar is type pointer-to-int. **The form int ar[] also means that ar is type pointer-to-int, but only when used to declare formal parameters. **
The idea is that the second form reminds the reader that not only does ar point to an int, it points to an int that’s an element of an array.
Because the name of an array is the address of the first element, an actual argument of an array name requires that the matching formal argument be a pointer.
In this context, and only in this context, C interprets int ar[] to mean the same as int * ar; that is, ar is type pointer-to-int.
Because prototypes allow you to omit a name, all four of the following prototypes are equivalent:
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
You can’t omit names in function definitions, so, for definitions, the following two forms are equivalent:
int sum(int *ar, int n)
{
// code goes here
}
int sum(int ar[], int n);
{
// code goes here
}
Dereferencing an Uninitialized Pointer: Do not dereference
an uninitialized pointer. For example, consider the following:
int * pt; // an uninitialized pointer
*pt = 5; // a terrible error
Why is this so bad? The second line means store the value 5 in the location to which pt points. But pt, being uninitialized, has a random value, so there is no knowing where the 5 will be placed. It might go somewhere harmless, it might overwrite data or code, or it might cause the program to crash.
A function working on an array needs to know where to start and stop.
The two expressions ar[i] and *(ar+i) are equivalent in meaning. Both work if ar is the name of an array, and both work if ar is a pointer variable.
However, using an expression such as ar++ only works if ar is a pointer variable.
You must use pointers if you want a function to affect variables in the calling function. The second use is in functions designed to manipulate arrays.
The usual rule is to pass quantities by value unless the program needs to alter the value, in which case you pass a pointer.
Arrays don’t give you that choice; you must pass a pointer. The reason is efficiency.
If a function passed an array by value, it would have to allocate enough space to hold a copy of the original array and then copy all the data from the original array to the new array.
It is much quicker to pass the address of the array and have the function work with the original data.
If a function’s intent is that it not change the contents of the array, use the keyword const when declaring the formal parameter in the prototype and in the function definition.
int sum(const int ar[], int n); /* prototype */
int sum(const int ar[], int n) /* definition */
{
int i;
int total = 0;
for( i = 0; i < n; i++)
total += ar[i];
return total;
}
It’s important to understand that using const this way does not require that the original array be constant; it just says that the function has to treat the array as though it were constant.
It prevents a function from modifying data in the calling function.
There are some rules you should know about pointer assignments and const.
First, it’s valid to assign the address of either constant data or non-constant data to a pointer-to-constant:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
const double * pc = rates; // valid
pc = locked; // valid
pc = &rates[3]; // valid
However, only the addresses of non-constant data can be assigned to regular pointers:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double locked[4] = {0.08, 0.075, 0.0725, 0.07};
double * pnc = rates; // valid
pnc = locked; // not valid
pnc = &rates[3]; // valid
This is a reasonable rule. Otherwise, you could use the pointer to change data that was supposed to be constant.
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
double * const pc = rates; // pc points to beginning of the array
pc = &rates[2]; // not allowed to point elsewhere
*pc = 92.99; // ok -- changes rates[0]
int (* pz)[2]; // pz points to an array of 2 ints
This statement says that pz is a pointer to an array of two ints.
Why the parentheses? Well, [ ] has a higher precedence than *. Therefore, with a declaration such as:
int * pax[2]; // pax is an array of two pointers-to-int
you apply the brackets first, making pax an array of two somethings. Next, you apply the *, making pax an array of two pointers. Finally, use the int, making pax an array of two pointers
to int. This declaration creates two pointers to single ints, but the original version uses parentheses to apply the * first, creating one pointer to an array of two ints.
you can use notation such as pz[2][1], even though pz is a pointer, not an array name.
More generally, you can represent individual elements by using array notation or pointer notation with either an array name or a pointer:
zippo[m][n] == *(*(zippo + m) + n)
pz[m][n] == *(*(pz + m) + n)
The rules for assigning one pointer to another are tighter than the rules for numeric types.
For example, you can assign an int value to a double variable without using a type conversion, but you can’t do the same for pointers to these two types:
int n = 5;
double x;
int * p1 = &n;
double * pd = &x;
x = n; // implicit type conversion
pd = p1; // compile-time error
int * pt;
int (*pa)[3];
int ar1[2][3];
int ar2[3][2];
int **p2; // a pointer to a pointer
Then we have the following:
pt = &ar1[0][0]; // both pointer-to-int
pt = ar1[0]; // both pointer-to-int
pt = ar1; // not valid
pa = ar1; // both pointer-to-int[3]
pa = ar2; // not valid
p2 = &pt; // both pointer-to-int *
*p2 = ar2[0]; // both pointer-to-int
p2 = ar2; // not valid
You can declare a function parameter of this type like this:
void somefunction( int (* pt)[4] );
// pt is a pointer to an array of four ints.
Alternatively, if (and only if) pt is a formal parameter to a function, you can declare it as follows:
void somefunction( int pt[][4] );
Note that the first set of brackets is empty. The empty brackets identify pt as being a pointer.
Recall that the compiler converts array notation to pointer notation. This means, for example, that ar[1] will become ar+1. For the compiler to evaluate this, it needs to know the size object to which ar points.
int sum2(int ar[][4], int rows); // valid declaration
says that ar points to an array of four ints
You can also include a size in the other bracket pair, as shown here, but the compiler ignores it:
int sum2(int ar[3][4], int rows); // valid declaration, 3 ignored
In general, to declare a pointer corresponding to an N-dimensional array, you must supply values for all but the leftmost set of brackets:
int sum4d(int ar[][12][20][30], int rows);
That’s because the first set of brackets indicates a pointer, whereas the rest of the brackets describe the type of data object being pointed to, as the following equivalent prototype illustrates:
int sum4d(int (*ar)[12][20][30], int rows); // ar a pointer
Here, ar points to a 12×20×30 array of ints.
VLAs Do Not Change Size:
The term variable in variable-length array does not mean that you can modify the length of the array after you create it. Once created, a VLA keeps the same size. What the term variable does mean is that you can use a variable when specifying the array dimensions when first creating the array.
First, here’s how to declare a function with a two-dimensional VLA argument:
int sum2d(int rows, int cols, int ar[rows][cols]); // ar a VLA
One point to note is that a VLA declaration in a function definition parameter list doesn’t actually create an array. Just as with the old syntax, the VLA name really is a pointer. This means a function with a VLA parameter actually works with the data in the original array, and therefore has the ability to modify the array passed as an argument.
int thing[10][6];
twoset(10,6,thing);
...
}
void twoset (int n, int m, int ar[n][m]) // ar a pointer to
// an array of m ints
{
int temp[n][m]; // temp an n x m array of int
temp[0][0] = 2; // set an element of temp to 2
ar[0][0] = 2; // set thing[0][0] to 2
}
When twoset() is called as shown, ar becomes a pointer to thing[0], and temp is created as a 10×6 array. Because both ar and thing are pointers to thing[0], ar[0][0] accesses the same data location as thing[0][0].
Variable-length arrays also allow for dynamic memory allocation. This means you can specify the size of the array while the program is running.
Suppose you want to pass a value to a function with an int parameter; you can pass an int variable, but you also can pass an int constant, such as 5. Before C99, the situation for a function with an array argument was different; you could pass an array, but there was no equivalent to an array constant. C99 changed that with the addition of compound literals.
Literals are constants that aren’t symbolic. For example, 5 is a type int literal, 81.3 is a type double literal, ‘Y’ is a type char literal, and “elephant” is a string literal.
For arrays, a compound literal looks like an array initialization list preceded by a type name that is enclosed in parentheses. For example, here’s an ordinary array declaration:
int diva[2] = {10, 20};
And here’s a compound literal that creates a nameless array containing the same two int values:
(int [2]){10, 20} // a compound literal
Because these compound literals are nameless, you can’t just create them in one statement and then use them later. Instead, you have to use them somehow when you make them.
One way is to use a pointer to keep track of the location. That is, you can do something like this:
int * pt1;
pt1 = (int [2]) {10, 20};
Another thing you could do with a compound literal is pass it as an actual argument to a function with a matching formal parameter:
int sum(const int ar[], int n);
...
int total3;
total3 = sum((int []){4,4,4,5,5,5}, 6);
Keep in mind that a compound literal is a means for providing values that are needed only temporarily. It has block scope, a concept covered in Chapter 12. That means its existence is not guaranteed once program execution leaves the block in which the compound literal is defined, that is, the innermost pair of braces containing the definition.
It’s often advantageous to write functions to process arrays; that helps modularize a program by locating specific tasks in specific functions.
It’s important to realize that when you use an array name as an actual argument, you’re not passing the entire array to the function; you are just passing the address of the array (hence, the corresponding formal parameter is a pointer).
The connection between arrays and pointers is an intimate one, and you can often represent the same operation using either array notation or pointer notation.
It’s this connection that allows you to use array notation in an array-processing function even though the formal parameter is a pointer, not an array.
a.
void process(double ar[], int n);
void processvla(int n, double ar[n]);
process(trots, 20);
processvla(20, trots);
b.
void process2(short ar2[30], int n);
void process2vla(int n, int m, short ar2[n][m]);
process2(clops, 10);
process2vla(10, 30, clops);
c.
void process3(long ar3[10][15], int n);
void process3vla(int n, int m,int k, long ar3[n][m][k]);
process3(shots, 5);
process3vla(5, 10, 15, shots);
#include
void res(double *p, int n);
int main()
{
double arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("original arr[10] = {");
for (int i=0; i<9; i++)
{
printf("%lf, ", arr[i]);
}
printf("%lf};\n", arr[9]);
res (arr, 10);
printf("reversed arr[10] = {");
for (int i=0; i<9; i++)
{
printf("%lf, ", arr[i]);
}
printf("%lf};\n", arr[9]);
return 0;
}
void res(double *p, int n)
{
double t[n];
for(int i=0; i
#include
void copy_ptr(int row, int col, float (*t)[col], float (*s)[col]);
void show(int row, int col, float (*p)[col]);
int main()
{
float source[2] [3] ={ {1.1, 2.2, 3.3}, {4.4, 5.5, 6.6} };
float target1[2] [3];
copy_ptr(2, 3, target1, source);
printf("target1[2] [3] = { {");
show(2, 3, target1);
return 0;
}
//void copy_ptr(int row, int col, float t[][col], float s[][col] )
void copy_ptr(int row, int col, float (*t)[col], float (*s)[col] )
{
for (int i=0; i|
#include
#define ROW 3
#define COL 5
void store(double arr[], int col);
double average(const double arr[], int col);
double aveall(int row, int col, const double arr[row][col]);
double max(int row, int col, const double arr[row][col]);
int main()
{
double data[ROW][COL];
int row;
for(row = 0; row < ROW; row++)
{
printf("Please enter %d numbers for row %d\n", COL, row+1);
store(data[row], COL);
}
for(row=0; row 0)
return sum/col;
else
return 0.0;
}
double aveall(int row, int col, const double arr[row][col])
{
int i, j;
double sum = 0.0;
for(i = 0; i< row; i++)
{
for(j = 0; j < col; j++)
{
sum += arr[row][col];
}
}
if ((row * col) > 0)
return sum / (row * col);
else
return 0.0;
}
double max(int row, int col, const double arr[row][col])
{
double max = arr[0][0];
int i, j;
for(i = 0; i < row; i++)
{
for(j = 0; j < col; j++)
{
if(max < arr[row][col])
max = arr[row][col];
}
}
return max;
}