使用const声明数组:有时需要把数组设置为只读。这样,程序就只能从数组中检索值,不能把新值写入数组中。
const int days[5] = {1, 2, 3, 4, 5};
数值示例:
#include<stdio.h>
int main(void) {
const int days[] = {31, 28, 31, 30, 31, 30 ,31 ,31 ,30 ,31};
int index;
//sizeof days是整个数组的大小,sizeof days[0]是数组中一个元素的大小
for(index=0; index<sizeof days / sizeof days[0]; index++){
printf("Month %2d has %d days.\n", index+1, days[index]);
}
return 0;
}
数组边界:在使用数组时,要确保程序中使用的数组的下标在规定范围内,因为编译器不会检查出这种错误(可能发出警告)。
#include<stdio.h>
int main(void) {
int value_1 = 44;
int arr[4];
int value_2 = 88;
int i;
printf("value_1=%d, value_2=%d\n", value_1, value_2);
for(i=-1; i<=4; i++){
arr[i] = 2 * i + 1;
}
for(i=-1; i<7; i++){
printf("%2d %d\n", i, arr[i]);
}
printf("value_1=%d, value_2=%d\n", value_1, value_2);
printf("address of arr[-1]: %p\n", &arr[-1]);
printf("address of arr[4]: %p\n", &arr[4]);
printf("address of value_1: %p\n", &value_1);
printf("address of value_2: %p\n", &value_2);
}
变相使用指针:数组名是数组首元素的地址。也就是说,如果filizny是一个数组,下面的语句成立。
filizny == &filizny[0];//数组名是该数组首元素的地址
filizny和&filizny[0]都表示数组首元素的内存地址(*是地址运算符)。两者都是常量,在程序的运行过程中不会改变。但是,可以把它们赋值给指针变量,然后可以修改指针变量的值。(转换说明%p通常以十六进制显示指针的值)
指针和数组:
#include<stdio.h>
#define SIZE 4
int main(void) {
short dates[SIZE];
short * pti;
short index;
double bills[SIZE];
double * ptf;
pti = dates; //把数组地址赋给指针
ptf = bills;
printf("%23s %15s\n", "short", "double");
for(index=0; index<SIZE; index++){
printf("pointers + %d: %10p %10p\n", index, pti+index, ptf+index);
}
return 0;
}
以下关系表明了数组和指针的关系十分密切,可以使用指针标识数组的元素和获得元素。从本质上看,同一个对象有两种表示法。实际上C语言标准在描述数组表示法时确实借助了指针。也就是说,定义ar[n]的意思是*(ar+n)。可以认为*(ar+n)的意思是"到内存的ar位置,然后移动n个单元,检索存储在那里的值"。
dates+2 == &dates[2] //相同的地址
*(dates+2) == dates[2] //相同的值
不要混淆*(dates+2)和*dates+2。间接运算符(*)的优先级高于+,所以*dates+2相当于(*dates)+2:
*(dates+2) //dates第3个元素的值
*dates+2 //dates第1个元素的值加2
示例:
#include<stdio.h>
#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++){
//*(days+index)与days[index]相同
printf("Month %2d has %d days.\n", index+1, *(days+index));
}
return 0;
}
声明数组形参:
因为数组名是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针。只有在这种情况下,C才会把int ar[]和int * ar解释成一样。也就是说,ar是指向int的指针。由于函数原型可以省略参数名,下面4种原型都是等价的。
int sum(int *ar, int n);
int sum(int *, int);
int sum(int ar[], int n);
int sum(int [], int);
指针形参:
该程序表明了指针形参是变量,这意味着可以用索引表名访问数组中的哪一个元素。
指针start开始执行marbles数组的首元素,所以赋值表示total+=*start把首元素(20)加给total。然后表达式start++递增指针变量start,使其指向数组的下一个元素。因为start是指向int的指针,start递增1相当于其递增int类型的大小。
#include<stdio.h>
#define SIZE 10
int sump(int *start, int *end);
int main(void){
int marbles[SIZE] = {20, 10, 5, 39, 4, 16, 19, 26, 31, 20};
long answer;
answer = sump(marbles, marbles+SIZE);
printf("The total number of marbles is %ld.\n", answer);
return 0;
}
//使用指针算法
int sump(int *start, int *end){
int total=0;
while(start<end){
total += *start; //把数组元素的值加起来
start++; //让指针指向下一个元素
}
return total;
}
指针运算中的优先级:
#include<stdio.h>
int data[2] = {100,200};
int moredata[2] = {300,400};
int main(void){
int *p1, *p2, *p3;
p1 = p2 = data;
p3 = moredata;
printf("*p1=%d, *p2=%d, *p3=%d\n", *p1,*p2,*p3);
printf("*p1++=%d, *++p2=%d, (*p3)++=%d\n", *p1++,*++p2,(*p3)++);
printf("*p1=%d, *p2=%d, *p3=%d\n", *p1,*p2,*p3);
}
解引用未初始化的指针:
int * pt; //未初始化的指针
*pt = 5; //严重的错误
为何不行?第2行的意思是把5储存在pt指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,导致程序崩溃。切记:创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。
const的其他用法:
声明一个不能指向别处的指针。
double rates[2] = {1, 2, 3};
double * const pc = rates; //pc指向数组的开始
pc = &rates[2]l; //不允许,因为该指针不能指向别处
*pc = 92.99 //没问题--更改rates[0]的值
声明一个不能更改它所指向的地址,也不能修改指向地址上的值
double rates[2] = {1, 2, 3};
const double * const pc = rates; //pc指向数组的开始
pc = &rates[2]l; //不允许
*pc = 92.99 //不允许
指针和多维数组:
#include<stdio.h>
int main(void){
int zippo[4][2] = { {2, 4}, {6, 8}, {1, 3}, {5, 7}};
printf("zippo[0][0] = %d\n", zippo[0][0]);
printf("*zippo[0] = %d\n", *zippo[0]);
printf("**zippo = %d\n", **zippo);
printf("zippo[2][1] = %d\n", zippo[2][1]);
printf("*(*(zippo+2)+1) = %d\n", *(*(zippo+2)+1));
return 0;vv
}
注意:
特别注意,与zippo[2][1]等价的指针表示法是*(*(zippo+2) +1)。
zippo <-二维数组首元素的地址
zippo+2 <-二维数组的第3个元素(即一维数组)的地址
*(zippo+2) <-二维数组的第3个元素(即一维数组)的首元素地址
*(zippo+2) + 1 <-二维数组的第3个元素(即一维数组)的第二个元素地址
*(*(zippo+2)+1) <-二维数组的第3个一维数组元素的第2个int类型元素的值,即数组的第3行第2列的值
变长数组(VLA):声明一个带二维变长数组参数的函数。
int sum(int rows, int cols, int ar[rows][cols]); //ar是一个变长数组
或
int sum(int, int, int ar[*][*]);
其函数的定义如下:
int sum(int rows, int cols, int ar[rows][cols]){
int r, c;
int tot = 0;
for(r=0; r<rows; r++){
for(c=0; c<cols; c++){
tot += ar[r][c];
}
}
return tot;
}
假设声明了下列数组:
int array1[5][4];
int array2[100][4];
int array3[2][4];
tot = sum2d(array1, 5); //5x4数组的元素之和
tot = sum2d(array2, 100); //100x4数组的元素之和
tot = sum2d(array3, 2); //2x4数组的元素之和
运用VLS:
#include<stdio.h>
#define ROWS 3
#define COLS 4
int sum2d(int rows, int cols, int ar[rows][cols]);
int main(void){
int i, j;
int rs=3;
int cs=10;
int junk[ROWS][COLS]={
{2, 4, 6, 8},
{3, 5, 7, 9},
{12, 10, 8, 6}
};
int morejunk[ROWS-1][COLS+2]={
{20, 30, 40, 50, 60, 70},
{5, 6, 7, 8, 9, 10}
};
int varr[rs][cs]; //变长数组(VLA)
for(i=0; i<rs; i++){
for(j=0; j<cs; j++){
varr[i][j] = i*j+j;
}
}
printf("3x5 array\n");
printf("Sum of all elements = %d\n", sum2d(ROWS, COLS, junk));
printf("2x6 array\n");
printf("Sum of all elements = %d\n", sum2d(ROWS-1, COLS+2, morejunk));
printf("3x10 array\n");
printf("Sum of all elements = %d\n", sum2d(rs, cs, varr));
return 0;
}
//带变长数组形参的函数
int sum2d(int rows, int cols, int ar[rows][cols]){
int r, c;
int tot=0;
for(r=0; r<rows; r++){
for(c=0; c<cols; c++){
tot += ar[r][c];
}
}
return tot;
}
复合字面量:
数组声明:int diva[2] = {10, 20};
下面的复合字面量创建了一和diva数组相同的匿名数组,也有两个int类型的值:
(int [2]){10, 20}; //复合面量
注意,去掉声明中的数组名,留下的int[2]即是复合字面量的类型名
也可以
(int []){50, 20, 90}; //内含3个元素的复合字面量
因为复合字面量是匿名的,所以不能先创建然后再使用它,必须在创建的同时使用它。使用指针记录地址就是一种用法:
int * pt1;
pt1 = (int [2]){10, 20};
还可以把复合字面量作为实际参数传递给带有匹配形式参数的函数:
int sum(const int ar[], int n);
....
int total3;
total3 = sum((int []){4,4,4,5,5,5}, 6);
创建二维int数组并储存其地址:
//声明一个指向二维数组的指针,该数组内含2个数组元素
int (*pt2)[4];
//每个元素是内含4个int类型值的数组
pt2=(int [2][4]){ {1,2,3,-9}, {4,5,6-8}};
最后:
记住,复合字面量是提供只临时需要的值的一种手段。复合字面量具有块作用域,这意味着一旦离开定义复合字面量的块,程序将无法保证该字面量是否存在。