10.1.1初始化数组
只储存单个值的变量有时也称为标量变量
int fix = i;
float flax = PI * 2;
代码中PI的定义为宏,C使用新的语法来初始化数组
int mian ()
{
int powers[8] = {1,2,4,6,7,8,16,32,64};//从ANSI C开始支持这种初始化
//...
}
根据初始化,把1的赋给数组的首元素power[0],以此类推(不支持ANSI的编译器会把这种形式的初始化识别为语法错误,在数组声明前加上关键字static可解决此问题
注意 使用const数组
需要把数组设置为只读时,程序只能从数组中检索值,不能把新值写入数组,用const声明和初始化数组。声明数组前加const。一旦声明为const数组便不能再给它赋值
注意 储存类别
数组和其他变量类似,可以把数组创建成不同的储存类别。本章描述的数组都是自动储存类别,即这些数组在函数内部声明,且声明时未使用关键字static
此处提到存储类别的原因是,不同的储存类别有不同的属性,所以不能把本章的内容推广到其他储存类别。对于一些其他储存类别的数组和变量,如果在声明时未初始化,编译器会自动把它们的值设置为0
10.1.2 指定初始化器(c99)
指定初始化器:可以初始化特定的数组元素
int arr[6] = {[5] = 212};//把arr[5]初始化为212
#include <stdio.h>
#define MONTHS 12
int main ()
{
int day[MONTHS] = {31,28,[4] = 31,30,31,[1] = 29};
int i ;
for (i = 0;i < MONTHS;i++)
printf("%2d $d\n",i+1,day[i]);
return 0;
},
1.如果指定初始化器后面有更多的值,后面的值将被用于初始化指定元素后面的元素,即days[4]被初始化后day[5],day[6]将被初始化为30和31
2.如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化
int stuff[] = {1,[6] = 23};
int stuff[] = {1,[6] = 4,9,10};
3.未指定数组大小时编译器会把数组的大小设置为足够装的下初始化的值。stuff数组有7个元素,编号为0-6,staff数组的元素比stuff数组多两个
10.1.3 给数组元素赋值
#include <stdio.h>
#define SIZE 50
int main ()
{
int counter, evens[SIZE];
for (counter = 0;counter < SIZE;counter++)
evens[counter] = 2*counter;
}
代码使用循环给数组赋值,C不允许把数组作为一个单元赋给另一个数组,除了花括号以外也不允许使用花括号列表的形式赋值
#define SIZE 5
int main ()
{
itn oxen[SIZE] = {5,3,2,8};
int year[SIZE];
yaks = oxen;//不允许
yaks[SIZE] = oxen{SIZE};//数组下标越界
yaks[SIZE] = {5,3,2,8};//不起作用
}
oxen数组的最后一个元素是oxen[SIZE]和yaks[SIZE]都超出了两个数组的末尾
10.1.4 数组边界
下面程序创建了一个内容4个元素的数组,然后错误地使用了-1到6的下标
#include <stdio.h>
#define SIZE 4
int main()
{
int value1 = 44;
int arr[SIZE];
int value2 = 88;
int i;
printf("value1 = %d,value2 = %d\n",value1,value2);
for (i = -1;i <= SIZE;i++)
arr[i] = 2*i + 1;
for(i = -1;i < 7 ;i++)
printf("%2d %d\n",i,arr[i]);
printf("value1 = %d, value2 = %d\n",value1,value2);
printf("address of arr[-1]: %p\n",&arr[-1]);
printf("address of arr[4]: %p\n",&arr[4]);
printf("address of value1: %p\n",&value1);
printf("address of value2: %p\n",&value2);
return 0;
}
编译器不会检查数组下标是否使用得当。在C的标准中,使用越界下标的结果是未定义的。这意味着程序看上去可以运行,但是运行的结果很奇怪或者异常终止。
程序中arr[-1]和value2对应的内存地址相同,arr[4]和value1对应的内存地址相同。因此使用越界的数组下标会导致程序改变其他变量的值,不同的编译器运行的结果可能不同,有些会导致程序异常终止。(原因P244上)
10.1.5 指定数组的大小
C99标准之前,声明数组时自能在方括号中使用整型常量表达式,特别的sizeof表达式被视为整形变量,但是const值不是。同时表达式的值必须大于0
int n = 5;
int m = 8;
float a8[n];
float a9[m];
C90标准的编译器不允许这种声明方式,而C99允许。这创建了一种新型数组,称为变长数组或简称VLA (P244下)
10.2 多维数组
/*计算每年的总降雨量,年平均降雨量和5年中每月的平均降水量*/
#include <stdio.h>
#define MONTHS 12
#define YEARS 5
int main ()
{
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}
};
int year ,month;
float subtot, total;
printf("YEAR RAINFALL (inches)\n");
for (year = 0,total = 0;year < YEARS;year++)
{ //每一年各月的降水量总和
for (month = 0,subtot = 0;month < MONTHS; month++)
subtot += rain[year][month];
printf("%5d %15.1f\n",2010 + year, subtot);
total += subtot; //5年的降水量总和
}
printf("\nThe yearly average is %.1f inches.\n\n",total / YEARS);
printf("MONTHLY AVERAGES:\n\n");
printf("Jan Feb Mar Apr May Jun Jul Aug Sep Oct");
printf("Nov Dec\n");
for (month = 0;month < MONTHS;month++)
{ //每个月,5年的总降水量
for (year = 0,subtot = 0;year<YEARS; year++)
subtot += rain[year][month];
printf("%4.1f", subtot / YEARS);
}
printf("\n");
return 0;
}
第一个嵌套for循环的内存循环,在year不变的情况下,遍历month数组计算某年的总降水量;外层循坏改变year的值,重复遍历month,计算5年的总降水量。常用于处理二维数组的循环结构:
for(year = 0,total = 0;year<YEARS;year++)
{
//处理每一年的数据
for(month = 0,suntot = 0;month<MONTHS;month++)
//...处理每月数据
//...处理每一年的数据
}
第二个嵌套循环同理,内存循环遍历year,外层循环遍历month
for (month = 0;month<MONTHS;month++)
{
//处理每月的数据
for(year = 0,subtot = 0;year<YEARS;year++)
//...处理每年的数据
//...处理每月的数据
}
10.2.1 初始化二维数组 (P247上)
int sq[2][3] = {{5,6},{7,8}}
int sq[2][3] = {5,6,7,8}
10.2.2 其他多维数组
int box[10][20][30];
把一维数组想象成一行数据,二维数组想象成数据表,三维数组想象成一叠数据表 。通常三维数组要使用三重循环,以此类推。
10.3 指针和数组
#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;
}
系统中地址按字节编址,short类型占用2字节,double类型占用8字节。在C中指针加1指的是增加一个储存单元。对数组而言加1后的地址是下一个元素的地址,而不是下一个字节的地址。这也是为什么必须声明指针所指向对象类型的原因之一。
date + 2 == &dates[2] //相同的地址
*(dates + 2) == dates[2]//相同的值
*(dates + 2) //dates第三个元素的地址
*dates + //dates第一个元素的值加2
#include <stdio.h>
#define MONTHS 12
int main ()
{
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)); //与days[index]相同
return 0;
}
此处days 是首元素的地址,days + index是元素days[index]的地址,而*(days + index)是该元素的值,相当于days[index]
10.4 函数,数组和指针
思考:假如要编写一个处理数组的函数,该函数返回数组中所有元素之和,待处理的是名为marbles的int类型数组,应该如何调用该函数
total = sum(marbles);//可能的函数调用
由于数组名是数组首元素的地址,则实际参数marbles是一个存储int类型值的地址,应该把它赋给一个指针形式参数,即该形参是一个指向int的指针
int sum(int *ar);//对应的函数原型
sum()从该参数获得了该数组首元素的地址,但该参数并未包含数组元素个数信息。有两种方法让函数获得这一信息:
1.在函数代码中写上固定的数组大小;
int sum(int * ar)//相应的函数定义
{
int i;
int total = 0;
for (i = 0;i < 10; i++)//假设数组有10个元素
total += ar[i]; //ar[i]与*(ar + i)相同
return total;
}
2.既然可以使用指针表示数组名,也可以用数组名表示指针。另一个比较灵活的方法是把数组大小作为第二个参数:
int sum(int * ar ,int n)//更通用的方法
{
int i;
int total = 0;
for (i = 0; i < n; i++)
total +=ar[i]; //ar[i]和*(ar+i)相同
return 0;
}
注意
关于函数的形参,只有在函数原型或函数定义头中,才可以用int ar[]代替int * ar;两种形式都表示ar是一个指向int的指针。但是int ar[]只能用于声明形式参数。int ar[]提醒读者指针ar指向的不仅仅是一个int类型值,还是一个int类型的指针。
注意 声明数组形参
由于函数原型可以省略参数名,所以下面4种函数原型都是等价的
int sum(int *ar,int n); int sum(int *, int); int sum(int ar[], int n); int sum(int [], int);
但是函数定义中不能省略参数名,下面两种形式的函数定义等价
int sum (int *ar, int n); { //... } int sum (int ar[],int n); { //... }
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main (void)
{
int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,23};
long answer;
answer = sum(marbles,SIZE);
printf("The total number of marbles is %ld.\n",answer);
printf("The size of marbles is %zd bytes.\n",sizeof marbles);
return 0;
}
int sum(int ar[], int n)//这个数组的大小是?
{
int i;
int total = 0;
for (i = 0; i < n; i++)
total +=ar[i];
printf("The size of ar is %zd bytes.\n",sizeof ar);
return total;
}
marbles含10个int类型的值,每个值站4个字节,所以整个marbles的大小是40个字节。ar是一个指向marbles数组首元素的指针,系统中用8字节存储地址,所以指针变量的大小是8字节(其它系统可能不是)
10.4.1 使用指针形参
sum()函数使用一个指针形参表示数组的开始,用一个整数形参表面待处理数组的元素个数(指针形参也表明了数组中的数据类型),但这不是函数传递必备信息的唯一方法。还有一种方法,第一个指针表面数组的开始处(和前面的用法相同),第二个指针指明数组的结束处。下面程序演示了这种方法,同时该程序也表明了指针形参是变量,这意味着可以使用索引表明访问数组中的哪一个元素。
#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;
}
sump()函数使用第二个指针来结束循环:while (start < end)。因为while循环的测试条件是一个不等式关系,所以循环最后处理的一个元素是所指向位置的前一个元素。这意味着end指向的位置实际上在数组最后一个元素的后面。C保证再给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得while循环的测试条件是有效的,因为start在循环中最后的值是end。注意,使用这种"越界"指针的函数调用更为简洁:
answer = sump(marbles + SIZE);
因为下标从0开始,所以marbles + SIZE指向数组末尾的下一个位置。如果end指向数组的最后一个元素而不是数组末尾的下一个位置,则必须使用下面的代码
answer = sump(marbles, marbles + SIZE - 1);
虽然C保证了marbles + SIZE有效,但是对marbles[SIZE](即存储在该位置的值)未作任何保证,所以程序不能访问该位置。
还可以把循环体压缩成一行代码:
total += *start++
++和*优先级相同,但结合律从右往左,所以先求++,让后算*。即指针先递增后指向
++start 先把指针指向位置上的值加到total上,让后在递增指针
*++start先递增指针,再使用指针指向位置上的值
(*start)++ 先使用start指向的值,再递增该值,而不是递增指针
*start++等同于*(start++)下面代码演示这些的优先级
#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);
return 0;
}
10.4.2 指针表示法和数组表示法 (P253)
10.5 指针操作
#include <stdio.h>
int main (void)
{
int urn[5] = {100,200,300,400,500};
int * ptr1, *ptr2, *ptr3;
ptr1 = urn; //把一个地址赋给指针
ptr2 = &urn[2]; //把一个地址赋给指针
//解引用指针,以及获得指针的地址
printf("pointer value dereferenced pointer address:\n");
printf("ptr1 = %p, *ptr1 =%d, &ptr1 =%p\n",ptr1,*ptr1,&ptr1);
//指针加法
ptr3 = ptr1 + 4;
printf("\nadding an int to a pointer:\n");
printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n",ptr1 + 4,*(ptr1 + 4));
ptr1++; //递增指针
printf("\nvalues after ptr1++:\n");
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n",ptr1, *ptr1, &ptr2);
ptr2--;
printf("\nvalues after --ptr2:\n");
printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n", ptr2, *ptr2, &ptr2);
--ptr1; //恢复为初始值
++ptr2; //恢复为初始值
printf("\nPointers reset to original values:\n");
printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2);
//一个指针减去另一个指针
printf("\nsubtracting one pointer from another:\n");
printf("ptr2 = %p, ptr1 = %p, ptr2 -ptr1 = %td\n",ptr2, ptr1, ptr2-ptr1);
//一个指针减去一个整数
printf("\nsunbtracting an int from a pointer:\n");
printf("ptr3 = %p,ptr3 - 2 = %p\n",ptr3, ptr3 - 2);
return 0;
}