c语言——数组篇

目录

数组简介

一维数组的定义及引用

一维数组的初始化

二维数组

二维数组的定义及引用

二维数组的初始化

多维数组

数组与函数

小结


数组简介

        我们都知道 c 有许多数据类型,如整型、实型和字符型等等.....它们都属于基本数据类型。除此之外,c 还提供了一些更为复杂的数据类型,名为构造类型或导出类型,它是由基本类型按照一定的规则组合而成,数组就是最其中基本的构造类型。

        数组是按顺序存储的一系列类型相同的值,如10个 char 类型的字符或10个 int 类型的值。整个数组有一个数组名,通过整数下标访问数组中单独的元素用于识别数组元素的数字被称为下标索引偏移量。下标必须是整数,而且下标是从0开始计数的。数组的元素依次存储在内存中相邻的位置。

一维数组的定义及引用

定义:

        定义一个数组,需要明确数组变量名,数组元素类型以及数组大小。一维数组定义一般形式为:

                类型名  数组名  [ 数组长度 ] ;

        //类型名指定数组中元素的类型;数组名是数组变量的名称,遵从标识符定义规则;数组长度是一个整型常量表达式,设定数组大小。

	int a[10];			   //含有10个整型元素的数组 a 
	double b[100];         //含有100个双精度浮点型元素的数组 b 
	char _xiaowei[10*10];  //含有100个字符型元素的数组 _xiaowei 

         在定义数组之后,系统根据数组中元素的类型及个数在内存中分配一段连续的存储单元用于存放数组各元素,并对这些单元进行连续编号,即下标,以区分不同的单元。每个单元所需的字节数由数组定义时给定的类型确定。如一个 int 类型占用4个字节,我们就假设上面定义的数组 a 的起始地址是 2000 ,则其内存分配形式为:

         还有一点需要注意的是,c 中数组名表示该数组所分配连续空间中第一个单元的地址,即数组名==首地址。另外由于数组空间一经分配之后在运行过程中不会改变,因此数组名是一个地址常量,不允许修改。这个一定要记住哦,因为以后说指针与数组区别,你记住这个数组名是地址常量就很容易理解为什么指针可以自增自减而数组不能。

引用:

        定义数组后,就可以使用它了。c 规定只能引用单个的数组元素,而不能一次引用整个数组,数组元素引用要指定下标,其形式为:

                数组名 [ 下标 ]

        下标可以是整型表达式,它的合理取值范围为[ 0 ~ 数组长度-1 ],使用数组时,要防止下标超出边界,也就是说必须确保下标是有效的值,例如” int  a[10] ;”那么在使用该数组时,要确保程序中的数组下标在 0 ~ 9 范围内,编译器不会替你去检查这种错误,在 c 标准中,使用越界下标的结果是未定义的,这意味这程序看上去可以运行,但是运行结果会很奇怪,或直接异常中止。

	int a[10],number=5;  //定义了整型变量number和整型数组a, 
	
	a[0]=number;		   
	a[number-1]=number*5;  
	a[number+3]=a[0];        //在可以使用整型变量的任何地方
	scanf("%d",&a[9]);       //都可以使用整型数组a的元素 

        需要注意区分的是数组的定义和元素的引用,两者都要用的 ”数组名  [  整型表达式 ]”。定义数组时,方括号内是常量表达式,代表数组长度,它可以包括常量和符号常量,但不能包含变量,也就是说数组的长度在定义时必须给定,在程序的运行过程中是不能改变的。而引用数组元素时,方括号内是表达式,代表下标,可以是变量。

        不过上面这段话是基于c99之前的标准,其实在c99开始,在定义数组时,我们已经可以用变量来定义数组的大小了。只是一旦创建之后数组大小是确定的,我们不能在程序运行过程中改变其大小这个没有变,这是数组的根本特点,程序运行过程中,数组不够大了,我们扩大一点,又不够大了,我们再扩大一点,这是不可能的诶。如为防止数组下标越界,我们可以让用户来给定数组的大小:

#include<stdio.h>
int main(void)
{
	printf("你所在的班级有多少人:");
	int size;
	scanf("%d",&size);
	printf("\n");
	int *mark[size];
	
	printf("请依次输入他(她)们的期末成绩,我会帮你记录下来:");
	int i;
	for(i=0; i<size; i++){      //用循环来遍历处理数组是我们经常使用的
		scanf("%d",&mark[i]);
	}
	printf("\n"); 
	

	for(i=0; i<size; i++){
		printf("%d ",mark[i]);
	}
	printf("好的,我记住了");
	
	return 0;
}

一维数组的初始化

        和简单变量的初始化一样,在定义数组时,也可以对数组元素赋初值。其一般形式为:

                类型名  数组名  [ 数组长度 ] =  {  初值表 } ;

        //初值表中依次放着数组元素的初值,如我们定义一个数组 a[ ],并对数组元素赋值:

        int  a [ 10 ] ={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, };

        //此时 a[ 0 ] == 1,a[ 1 ] == 2,....,a[ 9 ] == 10

        数组也可以只针对部分元素初始化,系统会自动给剩余未初始化元素赋0。当我们要把一个数组所有元素赋初值0,可以偷懒这样写: // int  a[ 10 ]= { 0, };

        还有一种初始化是 c99 开始才有的,c99 增加了一个新特性:指定初始化器。利用该特性可以初始化指定的数组元素,例如,我们只初始化数组 a 的第五个元素以及最后一个元素:       

#include<stdio.h>
int main(void)
{
	const int size=10;
	const int a[]={0,0,0,0,225,0,0,0,0,225};  //在c99标准之前,只能这样初始化 
	
	const int b[]={ [5] = 999,[9] = 999};    //a[5]、a[9]初始化为999,其他位置系统自动赋值0 
	
	
	
	int i;     					//循环遍历输入输出数组是我们经常用到的 
	printf("a[] = ");
	for(i=0; i<size; i++){
		printf("%d ",a[i]); 
	}	
	
	printf("\nb[] = ");
	for(i=0; i<size; i++){
		printf("%d ",b[i]); 
	}	
	
	return 0;
}

         源文件未编译?如果你也使用的是devc-++编译器,你可能和我有一样提示错误:sorry, unimplemented: non-trivial designated initializers not supported(程序未执行:不支持非平凡的指定初始化器),为什么会有这样的问题?当然书上说的指定初始化器是没错的,也不是devc-++不支持指定初始化,而是它底层用的编译器TDM-GCC貌似并不支持,designated initializers(指定初始化)是 c99 才有的,但是 c++ 是没有的,所以我们把后缀名改为.c再试试就可以了。

        

         当然你可能已经注意到了数组 a[ ] 定义初始化时数据类型 int 之前的const,首先数组名我们知道它地址常量,我们再在其之前加 const 的意思是表面数组内的每一个元素都是常量,一旦声明为 const 程序在运行过程中就不能修改该数组中的内容,有时我们需要把数组设置为只读,以保护数组中的元素,程序只能从数组中检索值而不能更改其值。还有数组定义初始化时我们省略了数组长度,我们可以这样做,我们可以让编译器替我们去数数,上述 a[ ] == a[ 10 ]。

二维数组

        c语言支持多维数组,其最常用的多维数组就是二维数组,主要用于表示二维表和矩阵。

二维数组的定义及引用

 二维数组的定义形式为

                   类型名 数组名  [ 行长度 ][ 列长度 ];

        如我们定义一个 3*3 的二维数组a:int a [3][3];  //3行3列,共9个元素;

引用二维数组的元素要指定两个下标,即行下标和列下标

                数组名  [ 行下标 ][ 列下标 ]

        行列下标合理取值范围应与一维数组一致,[ o ~ 行/列长度-1],切记下标不要越界。二维数组的元素在内存中按照行/列方式存放,我们习惯把二维数组想象成数据表:

二维数组的初始化

        在定义二维数组时,当然也可以对数组元素赋值,二维数组的初始化方法有两种。

分行赋初值:

                类型名  数组名  [ 行长度 ][ 列长度 ] = { {初值表0},....,{初值表n },.... };

//如初始化二维数组 a:  int a [3][3]={ {1,2,3},{4,5,6},{7,8,9} };

        同样二维数组的初始化也可以只针对部分元素,如  int a[3][3] = { {1,2,3} ,{ } ,{4,5} }; 只对第一行和第三行前两个元素赋值

顺序赋初值:

                类型名  数组名 [ 行长度 ][ 列长度 ] = { 初值表 };

//根据数组元素在内存中的存放顺序,把初值表中的数据依次赋给元素,如 int a[3][3]={ 1,2,3,4,5,6,7,8,9 };

//当然它也可以针对部分赋初值,只是我们很少这样,较容易出错,因为你要特别注意顺序 int a[3][3]={ 1,2,0,0,0,0,0,0,9 };    所以二维数组部分初始化还是分行比较直观清晰。

还有一点就是二维数组初始化可以省略行,我们可以让编译器替我们去数多少行,但是不能省略列,当然不是因为编译器比较懒,不给你数列了,行和列你总得给一个,不然人家怎么确定这个表格的形状。

多维数组

        好,然后我们再了解一下多维数组,前面讨论的二维数组的相关的内容都适用于三维数组或更多维度的数组。我们这样可以声明一个三维数组:

                int box [10][20][30];

        我们可以把一维数组想象成一行数据,把二维数组想象成数据表,把三维数组想象成一叠数据表,例如,把上述声明的三维数组 box 想象成由10个二维数组,每个二维数组都由20行30列组成,堆叠起来:

        我们处理二维数组是要用两层嵌套循环,所以我们处理三维数组就要使用三层嵌套循环,处理四维数组就要使用四层嵌套循环,其它多维数组以此类推,不过我们通常是很少用到除二维之上的多维数组。

数组与函数

        最后我们再聊一下,怎么将数组传入函数,其函数原型该是什么样子呢?假如我们要编写一个函数sun,其功能是计算一个int 数组的元素之和。我们调用函数是以数组名为实参(如我们计算 number 数组之和): sum(number)。那其函数原型呢?首先我们知道数组名是该数组首元素的地址,它是一个地址常量,所以实参 number 是一个存储 int 类型值的地址,则对应的形参应该是一个指向 int 的指针,所以其对应函数原型应为:int sum(int * begin );

        那么我们就会传了,但是还有一个问题,sum()获得了该数组首元素的地址,知道了从何处开始,但是它并不知道数组有多大,所以数组的最大有效下标 length-1 我们也就不得而知了,其参数并未包含数组元素个数的信息,所以我们通常把数组传入函数时还会将其长度作为第二个参数传入函数: int sum(int * begin ,int length);  

#include<stdio.h>
int sum(int *,int ); 
int main(void)
{
	const int size=10;
	int number[size];
	int i;
	for(i=0; i<=size; i++){    //我们从一加到十吧 
		number[i]=i+1;
	}
	
	printf("sum = %d\n",sum(number,sizeof(number)/sizeof(number[0])));
	return 0;
}

int sum(int * begin,int length)
{
	int sum=0;
	int i;
	for(i=0; i<10; i++){
		sum+=begin[i];        
	}
	return sum;
 } 

        关于上述代码有两点说一下,一是求数组的长度,我们求数组的长度时通常是用sizeof()来求,而不是直接写一上一个字面量,为的是方便以后,如修改了数组大小,我们可以不用在去该其函数参数,sizeof ( 数组名 ) / sizeof ( 数组名[0] ) 永远是该数组的大小,我们知道 sizeof ()返回的的是其参数字节大小,sizeof(数组名)所占字节位数,如上述 number 是一个 int 型数组,其每一元素是一个 int,一个 int 占4个字节,该数组含有10个int,所以sizeof(number)返回值== 40,sizeof( number[0] )是其数组第一个元素,也是一个 int,相除就是 40/4==10 便是该数组的大小。

        还有就是上述代码中我们调用函数时传入数组名,sum函数用一个指向 int 的指针接收,而后我们在求和 sum+=begin[i];   又用表示数组的 [ ] 将其当成数组来用,我们可以这样使用,begin [ i ]  等价于 *( begin + i) , 我们知道 begin 是 数组首地址,第一轮循环,i=0;begin + i 就是首地址加0,所以还是首地址,然后*解引用(取其地址上所存放的值),就是begin [0] ,第二轮就 begin + i( i=1 )==begin[ 1 ]指针加一的意思是它当前位置的下一个位置,并不是单纯的加一,假设一个 int * a 的指针值(地址)为 2000,a+1 的值就应为 2004,为什么是 2004 呢?咱们好好想想,一个 int 占多少个字节?一个 int 占四个字节对吧,所以 2000 的下一个位置就是 2004,如果a指向一个 double 双精度浮点数呢?那 a+1 就是 2008(double 占8个字节)。

        所以我们可以有指针表示数组名,也可以用数组名表示指针,数组是特殊的指针(const 的指针),但也并不完全相同,我们就不扩展。既然它们本就可以相互表示,那么我们就有第二种声明形式: int sum(int begin[ ] ,int length); 

        不过要注意的是,关于函数的形参,只有在函数原型或函数定义头中才可以用 int begin[ ]  代替 int * begin, int begin[ ]  和 int * begin 形式都表示 begin 是一个指向 int 的指针,但是 int begin[ ] 只能用于声明形式参数,int begin[ ] 形式提醒读者指针 begin 指向的不仅仅是一个 int 类型值,还有一个 int 类型数组的元素。

小结

        由以上我们得知,处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,我们可以选择是使用数组表示法还是指针表示法。使用数组表示法让函数时处理数组这一意图更加明显,通过理解我们可能觉得使用指针自然,至于 c 语言,begin [ i ] 和 *(begin +i)是等价的,无论 begin 是数组名还是指针变量,这两个表达式都没问题。