一、数组的定义
数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。这些有序排列的同类数据元素的集合称为数组。
二、数组的初始化
数组的初始化是指在创建数组的同时给数组的内容一些合理初始值(初始化)。
int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
数组在创建的时候如果想不指定数组的确定大小就得初始化。数组的元素个数根据初始化的内容来确定。
注:char型数据是将一个字符常量放到一个字符变量中,并不是把该字符本身放到内存单元中去,而是将该字符的相应的ASCII代码放到存储单元中。因为b的ASCII码值是98,所以arr4和arr5所存储的内容是完全一致的。
对于下面的代码要区分,内存中是如何分配的呢
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
arr1以字符串的方式初始化,在数组的结尾会自动添加上’\0’,而arr2的方式则不会,只能自己在初始化的结尾加上’\0’才能完成初始化操作。
三、给数组元素赋值
声明数组后,可以借助数组下标(或索引)给数组元素赋值。例如,下面的程序段给数组的所有元素赋值:
/给数组的元素赋值/
#include<stdio.h>
#define SIZE 50
int main(void)
{
int counter,evens[SIZE];
for(counter=0; counter<SIZE; counter++)*
evens[counter]=2counter;
...
}
这段代码中使用循环给数组的元素依次赋值。注意:
(1)C不允许把数组作为一个单元赋给另一个数组。
(2)除初始化以外不允许使用花括号列表的形式赋值。
下面的代码段演示了一些错误的赋值形式:
/一些无效的数组赋值/
#define SIZE 5
int main(void)
{
int oxen[SIZE]={5,3,2,8};/初始化没问题/
int yaks[SIZE];
yaks=oxen;/不允许/
yaks[SIZE]=oxen[SIZE];/数组下标越界/**
四、数组边界
在使用数组时,要防止数组下标超出边界,也就是说,必须确保下标是有效的值。
下面的程序创建了一个内含四个元素的数组,然后错误的使用了-1~6的的下标:
#include
#define SIZE 4
int main(void)
{
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标准中,使用越界下标的
结果是未定义的。这意味着程序看上去可以运行,但是运行结果很奇怪,或
异常中止。下面是使用GCC的输出示例:
value1=44,value2=88
-1 -1
1 3
2 5
3 7
4 9
5 1624678494
6 32767
value1=9, value2=-1
address of arr[-1]: 0x7fff5fbff8cc
address of arr[4]: 0x7fff5fbff8e0
address of value1: 0x7fff5fbff8e0
address of value2: 0x7fff5fbff8cc
注意,该编译器似乎把value2储存在数组的前一个位置,把value1储存在数组的后一个位置(其他编译器在内存中储存数据的顺序可能不同)。在上面的输出中,arr[-1]与value2对应的内存地址相同,arr[4]和value1对应的内存地址相同。因此,使用越界的数组下标会导致程序改变其他变量的值。
不同的编译器运行该程序的结果可能不同,有些会导致程序异常中止。
五、指定数组的大小
使用整型常量来声明数组:
#define SIze 4
intmain(void)
{
intarr[SIZE];//整数符号常量
doublelots[144];//整数字面常量
...
在C99标准之前,声明数组时只能在方括号中使用整型常量表达式。所
谓整型常量表达式,是由整型常量构成的表达式。sizeof表达式被视为整型
常量,但是(与C++不同)const值不是。另外,表达式的值必须大于0:
上面的
int n=5
int m=8;
float a1[5];//可以
float a2[5*2+1];//可以
float a3[sizeof(int)+1];//可以
float a4[-4];//不可以,数组大小必须大于0
float a5[0];//不可以,数组大小必须大于0
float a6[2.5];//不可以,数组大小必须是整数
float a7[(int)2.5];//可以,已被强制转换为整型常量
float a8[n];//C99之前不允许
float a9[m];//C99之前不允许
注释表明,以前支持C90标准的编译器不允许后两种声明方式。
而C99标准允许这样声明,这创建了一种新型数组,称为变长数组
(variable-lengtharray)或简称VLA(C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性)。
C99引入变长数组主要是为了让C成为更好的数值计算语言。例如,VLA简化了把FORTRAN现有的数值计算例程库转换为C代码的过程。VLA有一些限制,例如,声明VLA时不能进行初始化。在充分了解经典的C数组后,我们再详细介绍VLA。
六、一维数组
1.数组的创建
(1)数组是一组相同类型元素的集合。 数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
(2)数组创建的实例:
//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组时候可以正常创建?
//代码3
char arr3[10];
float arr4[1];
double arr5[20];
注:上面的代码2,在用 gcc 去编译这段程序时,是没有任何问题的,但在vs2019环境下会报以下的错误:-1
是因为在 ISO/IEC9899 标准的 6.7.5.2 Array declarators 中明确说明了数组的长度可以为变量的,称为变长数组(VLA,variable length array)。(注:这里的变长指的是数组的长度是在运行时才能决定,但一旦决定在数组的生命周期内就不会再变。)
在 GCC 标准规范的 6.19 Arrays of Variable Length 中指出,作为编译器扩展,GCC 在 C90 模式和 C++ 编译器下遵守 ISO C99 关于变长数组的规范。
注意:在我们学习的vs2019环境下,数组创建, [] 中要给一个常量才可以,不能使用变量。
2.一维数组的使用
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。 我们来看代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
//计算数组的元素个数
int sz = sizeof(arr)/sizeof(arr[0]);
//对数组内容赋值,数组是使用下标来访问的,下标从0开始。
int i = 0;//做下标
for(i=0; i<10; i++)
{
arr[i] = i;
}
//输出数组的内容
for(i=0; i<10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
总结:
(1)数组是使用下标来访问的,下标是从0开始。
(2)数组的大小可以通过sizeof操作符计算得到。
int arr[10];
int size =sizeof(arr)/sizeof(arr[0]);
3.一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。 看代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
输出结果如下:
&arr[0]= 007EFE04
&arr[1]= 007EFE08
&arr[2]= 007EFE0C
&arr[3]= 007EFE10
&arr[4]= 007EFE14
&arr[5]= 007EFE18
&arr[6]= 007EFE1C
&arr[7]= 007EFE20
&arr[8]= 007EFE24
&arr[9]= 007EFE28
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。 由此可以得出结论:数组在内存中是连续存放的。
七、指定初始化器(C99)
C99增加了一个新特性:指定初始化器(designatedinitializer)。利用
该特性可以初始化指定的数组元素。例如,只初始化数组中的最后一个元
素。对于传统的C初始化语法,必须初始化最后一个元素之前的所有元素,
才能初始化它:
intarr[6]={0,0,0,0,0,212};//传统的语法
而C99规定,可以在初始化列表中使用带方括号的下标指明待初始化的
元素:
intarr[6]={[5]=212};//把arr[5]初始化为212
对于一般的初始化,在初始化一个元素后,未初始化的元素都会被设置
为0。程序清单中的初始化比较复杂。
程序清单designate.c程序
//designate.c--使用指定初始化器
#include<stdio.h>
#defineMONTHS12
intmain(void)
{
intdays[MONTHS]={31,28,[4]=31,30,31,[1]
=29};
inti;
for(i=0;i<MONTHS;i++)
printf("%2d%d\n",i+1,days[i]);
return0;
}
该程序在支持C99的编译器中输出如下:
1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0
9 0
10 0
11 0
12 0
以上输出揭示了指定初始化器的两个重要特性。第一,如果指定初始化器后面有更多的值,如该例中的初始化列表中的片段:[4]=31,30,31,那么
后面这些值将被用于初始化指定元素后面的元素。也就是说,在days[4]被初始化为31后,days[5]和days[6]将分别被初始化为30和31。第二,如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。例如程序清单开始时把days[1]初始化为28,但是days[1]又被后面的指定初始化[1]=29初始化为29。
八、二维数组
1.导入
我们要在气象分析程序中用到这个二维数组。该程序的目标是,计算每年的总降水量、年平均降水量和月平均降水量。要计算年总降水量,必须对一行数据求和;要计算某月份的平均降水量,必须对一列数据求和。
2.二维数组的创建
//数组创建
int arr3;
char arr3;
double arr2;
/rain.c--计算每年的总降水量、年平均降水量和5年中每月的平均降水量/
#include <stdio.h>
#define MONTHS 12//一年的月份数
#define YEARS 5//年数
int main(void)
{
//用2010~2014年的降水量数据初始化数组
const float rainYEARS=
{
{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, subtotal = 0 ; month < MONTHS ; month++)
subtotal += rainyear;
printf("%5d%15.1f\n",2010+year,subtot);
total+=subtot;//5年的总降水量
}
printf("\n The yearly average is%.1 finches.\n\n",total/YEARS);
printf("MONTHLYAVERAGES:\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+=rainyear;
printf("%4.1f",subtot/YEARS);
}
printf("\n");
return0;
}
下面是该程序的输出:
YEAR RAINFALL(inches)
2010 32.4
2011 37.9
2012 49.8
2013 44.0
2014 32.9
They early average is 39.4 inches.
MONTHL YAVERAGES:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
7.3 7.3 4.9 3.0 2.3 0.6 1.2 0.3 0.5 1.7 3.6 6.7
学习该程序的重点是数组初始化和计算方案。初始化二维数组比较复杂,我们先来看较为简单的计算部分。
程序使用了两个嵌套for循环。第1个嵌套for循环的内层循环,在year不变的情况下,遍历month计算某年的总降水量;而外层循环,改变year的值,重复遍历month,计算5年的总降水量。这种嵌套循环结构常用于处理二维数组,一个循环处理数组的第1个下标,另一个循环处理数组的第2个下标:
for(year=0,total=0;year<YEARS;year++)
4.二维数组的初始化
float rain5;//内含5个数组元素的数组,每个数组元素内含12个float类型的元素
理解该声明的一种方法是,先查看中间部分(粗体部分):
float rain5;//rain是一个内含5个元素的数组
这说明数组rain有5个元素,至于每个元素的情况,要查看声明的其余部分(粗体部分):
float rain[5][12];//一个内含12个float类型元素的数组
这说明每个元素的类型是float[12],也就是说,rain的每个元素本身都是一个内含12个float类型值的数组。
根据以上分析可知,rain的首元素rain[0]是一个内含12个float类型值的数组。所以,rain[1]、rain[2]等也是如此。如果rain[0]是一个数组,那么它的首元素就是rain0,第2个元素是rain0,以此类推。简而言之,数组rain有5个元素,每个元素都是内含12个float类型元素的数组,rain[0]是内含12个float值的数组,rain0是一个float类型的值