微软Visual Studio产品之Visual C++编程进阶——一维数组(画画版)-LMLPHP

我是荔园微风,作为一名在IT界整整25年的老兵,看到不少初学者在学习编程语言的过程中如此的痛苦,我决定做点什么,我小时候喜欢看小人书(连环画),在那个没有电视、没有手机的年代,这是为数不多的课余生活方式这一。我画的不好,但是如果能帮大家理解编程语言,那我的目的就达到了,希望初学者少走弯路。下面我们就开始吧。

对于简单的问题,使用整型、字符型、浮点型这些简单的数据类型就可以了。但是,对于有些需要处理的数据,只用这些简单的数据类型是不够的,难以反映出数据的特点,也难以有效地进行处理。比如要处理一个工厂的1000个零件数据,一个一个定义变量工作量不可想象,也没有反映出这些数据间的内在联系,实际上这些数据是具有相同的属性。

微软Visual Studio产品之Visual C++编程进阶——一维数组(画画版)-LMLPHP

人们想出这样的办法:既然它们都是同一类性质的数据,就可以用同一个名字(如s)来代表它们,而在名字的右下角加一个数字来表示这是第几个数据。这个右下角的数字称为下标。一批具有同名的同属性的数据就组成一个数组(array),s就是数组名。

数组是一组有序数据的集合。数组中各数据的排列是有一定规律的,下标代表数据在数组中的序号。用一个数组名(如s)和下标来唯一地确定数组中的元素。数组中的每一个元素都属于同一个数据类型。不能把不同类型的数据放在同一个数组中。

C语言规定用方括号中的数字来表示下标,如用s[1000]表示s1000。将数组与循环结合起来,可以有效地处理大批量的数据,大大提高了工作效率。

微软Visual Studio产品之Visual C++编程进阶——一维数组(画画版)-LMLPHP

一维数组是数组中最简单的,它的元素只需要用数组名加一个下标,就能唯一地确定。要使用数组,必须在程序中先定义数组,即通知计算机:由哪些数据组成数组,数组中有多少元素,属于哪个数据类型。否则计算机不会自动地把一批数据作为数组处理。例如,下面是对数组的定义:

int a[10];

它表示定义了一个整型数组,数组名为a,此数组有10个整型元素。

定义一维数组的一般形式为

类型符 数组名[常量表达式];

说明:数组名的命名规则和变量名相同,遵循标识符命名规则。在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。例如,指定a[10],表示a数组有10个元素。

注意,下标是从0开始的,这10个元素是:a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9]。

微软Visual Studio产品之Visual C++编程进阶——一维数组(画画版)-LMLPHP

请注意,按上面的定义,不存在数组元素a[10]。常量表达式中可以包括常量和符号常量,如“int a[3+5];”是合法的。不能包含变量,如“int a[n];”是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。

例如,下面这样定义数组是不行的:

int n;
scanf("%d", &n) ;  //企图在程序中临时输入数组的大小
int a[n];

经过上面的定义,在内存中划出一片存储空间,存放了一个有10个整型元素的数组(如果用VisualC++,此空间大小为4*10=40字节)。可以看到,用一个“int a[10];”,就相当定义了10个简单的整型变量,显然简捷方便。

a数组
a[0]  a[1]  a[2]  a[3]  a[4]  a[5]  a[6]  a[7]  a[8]  a[9]

说明:如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。如:

void func(int n)
{
  int a[100*n];  //合法,n的值从实参传来
}

在调用func函数时,形参n从实参得到值。这种情况称为“可变长数组”,允许在每次调用func函数时,n有不同的值。但是在执行函数时,n的值是不变的,数组长度是固定的。

如果指定数组为静态(static)存储方式,则不能用“可变长数组”。如:

static int a[100*n]:  //不合法,a数组指定为static存储方式

在定义数组并对其中各元素赋值后,就可以引用数组中的元素。应注意:只能引用数组元素而不能一次整体调用整个数组全部元素的值。

引用数组元素的表示形式为

数组名[下标]

例如,a[0]就是数组a中序号为0的元素,它和一个简单变量的地位和作用相似。“下标”可以是整型常量或整型表达式。每一个数组元素都代表一个整数值。

注意:定义数组时用到的“数组名[常量表达式]”和引用数组元素时用的“数组名[下标]”形式相同,但含义不同。

例如:

int a[10];   //这里的a[10]表示的是定义数组时指定数组包含10个元素
m=a[6];    //这里的a[6]表示引用a数组中序号为6的元素

例 对10个数组元素依次赋值为0,1,2,3,4,5,6,7,8,9,要求按逆序输出。

解题思路:显然首先要定义一个长度为10的数组,由于赋给的值是整数,因此,数组可定义为整型,要赋的值是0~9,有一定规律,可以用循环来赋值。同样,用循环来输出这10个值,在输出时,先输出最后的元素,按下标从大到小输出这10个元素。这个算法很简单,可以直接写出程序。

编写程序:

#include <stdio.h>
int main()
{
  int i, a[10];
  for(i=0; i<=9;i++)
   a[i]=i;
  for(i=9;i>=0; i--)
   printf("%d", a[i]);
  printf("\n")
  return 0;
}

运行结果:
9876543210


程序分析:第1个for循环使a[0]~a[9]的值为0~9,第2个for循环按a[9]~a[0]的顺序输出各元素的值。

应当特别提醒的是:数组元素的下标从0开始,如果用“int a[10];”定义数组,则最大下标值为9,不存在数组元素a[10]。在定义数组的同时,给各数组元素赋值,这称为数组的初始化。可以用“初始化列表”方法实现数组的初始化,一共有四种形式,请大家记住。

(1)在定义数组时对全部数组元素赋予初值。例如:

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

将数组中各元素的初值顺序放在一对花括号内,数据间用逗号分隔。花括号内的数据就称为“初始化列表”。经过上面的定义和初始化之后,a[0]=0,a[1]=1,a[2]=2,a[3]=3,a[4]=4,a[5]=5,a[6]=6,a[7]=7,a[8]=8,a[9]=9。

(2)可以只给数组中的一部分元素赋值。例如:

int a[10]={0,1,2,3,4} ;

定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前面5个元素赋初值,系统自动给后5个元素赋初值为0。

(3)如果想使一个数组中全部元素值为0,可以写成

int a[10]={0,0,0,0,0,0,0,0,0,0}; 

或int a[10]={0};  //未赋值的部分元素自动设为0

(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长度。例如:

int a[5]={1,2,3,4,5};

可以写成
int a[]={1,2,3,4,5};

在第2种写法中,花括号中有5个数,虽然没有在方括号中指定数组的长度,但是系统会相据花括号中数据的个数确定a数组有5个元素。但是,如果数组长度与提供初值的个数不相同,则方括号中的数组长度不能省略。例如,想定义数组长度为10,就不能省略数组长度的定义,而必须写成

int a[10]={1,2,3,4,5);

只初始化前5个元素,后5个元素为0

说明:

如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统会自动把它们初始化为0。

如果是字符型数组,则初始化为'\0'。

如果是指针型数组,则初始化为NULL,即空指针。

一维数组程序举例

例 用数组来处理求 Fibonacci数列问题。

解题思路:如果用数组来处理,每一个数组元素代表数列中的一个数,依次求出各数并存放在相应的数组元素中即可。

编写程序:

#include <stdio.h>

int main()
{
  int i;
  int f[20]={1,1};  //对最前面两个元素[0]和[1]赋初值1

  for(i=2;i<20;i++)
   f[i]=f[i-2]+f[i-1];  //先后求出f[2]~f[19]的值

  for(i=0;i<20;i++)
  {
  if(i%5==0) printf("\n");  //控制每输出5个数后换行
  printf("%12d", f[i])  //输出一个数
  }
  printf("\n");
  return 0;
}

运行结果:
  1  1  2  3  5
  8  13  21  34  55
  89  144  233  377  610
  987  1597  2584  4181  6765

程序分析:为节约篇幅,程序只计算20个数。定义数组长度为20,对最前面两个元素f[0]和f[1]均指定初值为1,根据数列的特点,由前面两个元素的值可计算出第3个元素的值,即:
f[2]=f[0]+[1] 

在循环中可以用以下语句依次计算出[2]~f[19]的值
[i]=[i-2]+[i-1]

if语句用来控制换行,每行输出5个数据。


例 对10个数字按由小到大的顺序排列。

解题思路:这种问题称为数的排序(sort)。排序的规律有两种:一种是“升序”,从小到大;另一种是“降序”,从大到小。可以把这个题目抽象为一般形式“对n个数按升序排序”。

排序方法是一种重要的、基本的算法。排序的方法很多,本例用“冒泡法排序”。基本思路是:每次将相邻两个数比较,将小的调到前头。若有6个数:19,18,15,14,12,10。第1次先将最前面的两个数18和19对调。第2次将第2和第3个数(19和15)对调……如此共进行5次,得到18-15-14-12-10-19的顺序,可以看到:最大的数19已“沉底”,成为最下面一个数,而小的数“上升”。最小的数10已向上“浮起”一个位置。经过第1趟(共5次比较与交换)后,已得到最大的数19。然后进行第2趟比较,对余下的前面5个数(18,15,14,12,10)进行新一轮的比较,以便使次大的数“沉底”。按以上方法进行第2趟比较,经过这一趟4次比较与交换,得到次大的数18。

按此规律进行下去,可以推知,对6个数要比较5趟,才能使6个数按大小顺序排列。在第1趟中要进行两个数之间的比较共5次,在第2趟过程中比较4次……第5趟只须比较1次。如果有n个数,则要进行n-1趟比较。在第1趟比较中要进行n-1次两两比较,在第j趟比较中要进行n-j次两两比较。这如同水底的气泡逐步冒出水面一样,故称为“冒泡法”。


编写程序:

#include <stdio. h>
int main()
{
  int a[10];
  int i,j,t;
  printf("input 10 numbers \n");
  for (i=0;i<10;i++ )
   scanf("%d",&a[i]) ;
  printf("\n");
  forj=0;j<9:j++)  //进行9次循环,实现9趟比较
    for(i=0;i<9-j;i++)  //在每一趟中进行9-j次比较
       if (a[i]>a[i+1] )  //相邻两个数比较
          {t=a[i]; a[i]=a[i+1]; a[i+1]=t;}
  printf("the sorted numbers \n");
  for(i=0;i<10;i++)
    printf("%d", a[i])
  printf("\n");
  return 0;
}

运行结果:
input 10 numbers :
30 68 90 03 120 88 65 99 158 24
the sorted numbers
3 24 30 65 68 88 90 99 120 158

程序分析:当执行外循环第1次循环时,j=0,然后执行第1次内循环,此时i=0,在if语句中将a[i]和a[i+1]比较,就是将a[0]和a[1]比较。执行第2次内循环时,i=1,a[i]和a[i+1]比较,就是将a[1]和a[2]比较……执行最后一次内循环时,i=8,a[i]和a[i+1]比较,就是将a[8]和a[9]比较。这时第1趟过程完成了。当执行第2次外循环时,j=1,开始第2趟过程。内循环继续的条件是i<9-j,由于j=1,因此相当于i<8,即i由0变到7,要执行内循环8次。其余类推。

但是由于数据库等技术的发展,这个数组在实际的运用中,并不是用来存放大量结构化数据的,比如你要做一个电商软件,不可能用数组来存放大量电商数据,比如订单、购买记录等等。

目前数组主要用在操作系统内核、中间件领域、程序中的数据结构组合使用、临时使用的同质数据、缓存结构等地方,这一点大家一定要注意。

如果你有志向研究系统底层软件,请好好学习数据结构。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

01-12 08:26