第三章 数 组

第三章 数  组

 

在前面的各个章节中,已经介绍了C++中的几种基本机构和基本数据类型,经过练习,可以实现很多有意思的程序,完成了一些简单而有趣的程序。但在程序设计中,仅依靠基本类型和单纯的三种基本程序结构并不能够实现更多更强大的功能。所以做为程序设计者必须将更多更强大的构造数据类型和更为复杂的程序结构引入到程序设计当中,才能够让程序更强大,更能够满足程序设计人员的需求。

复杂的数据类型有很多,如结构体,指针等,都将在后面的章节中介绍。在本章中,将会详细介绍第一种构造数据类型——数组。

第一节  一维数组的定义与引用

【学习目标:】

l         了解数组的存在使用价值

l         掌握数组的定义及使用方法

l         掌握数组使用的基本技巧

【知识准备】

经过对前面章节的学习,可以了解到,在程序的编写过程中,需要定义很多个变量来存储一系列在程序运行过程中需要存储和处理的数据,而独立的定义不同的变量在程序中使用,会带来越来越的困惑。特别是在学习了循环以后,循环变量的独立定义是可以理解和合理的,但是,本来是一连串有规律的输入数据,却需要定义大量的变量来一个一个的读取或者利用一些技巧来避免存储,这样的情况,似乎是给有强大功能的循环语句带上了沉重的枷锁,无法发挥其应有的能力。

为了解决这样的矛盾,在程序设计中引入了数组的概念。把若干相同类型的变量以有序的形式组织起来,而这个同类有序数据元素的集合被称为数组。正因为一个数组可以被分解为多个元素,这个元素可以是基本类型或者是其它类型,所以数组是我们接触到的第一种构造类型。

 

1、一维数组的定义

在C/C++中,数组的使用和基本类型的使用一样,必须首先进行定义,然后才能够使用。而按照数组的维数划分,数组可以划分为一维数组和多维数组,一维数组的定义方式为:

类型说明符 数组名称[数组长度]

 

其中类型说明符是任意一种数据类型,包括基本数据类型或构造数据类型;

数组名称是程序设计者定义的数组表示符;

方括号中的数组长度即为数组所包含的元素个数。

例如:

int shu[10];            定义整数类型数组shu,有10个元素。

    float sum[10],jun[20];   说明实型数组sum,有10个元素,实型数组jun,有20个元素。

char str[20];          说明字符数组str,有20个元素。

 

在数组的定义中,有几点需要注意:

(1)数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的。

(2)数组名称的书写规则应符合标识符的书写规定。特别应当注意,数组名称应能够表现出一定的存储意义,但不要过长。

(3)允许在同一个类型说明中,说明多个数组和多个变量。如上面的:

float sum[10],jun[20];

    (4)方括号中常量表达式表示数组元素的个数,如shu[5]表示数组shu有 5个元素。但是其下标从0开始计算。因此5个元素分别为shu[0],shu[1],shu[2],shu[3],shu[4]。

    (5)在C语言中,不能够在方括号中用变量来表示数组长度,但C++中允许。如在turbo c中,下面的程序是错误:

      main()

          {

            int n=5;

            int a[n];

            ……

}

        但在dev-c++中,下面的程序是正确的。

          #include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

                  int i;

                  i=5;

                  int shu[i];

                  ......

                  system("PAUSE");

                  return EXIT_SUCCESS;

}

 

2、一维数组的引用

数组的基本组成单元是数组元素,每个数组元素也就是一个变量,其标示方法为在数组名称后跟一个带方括号的下标。下标表示该元素在数组里的序号。具体的形式如下:

数组名称[下标]

其中的下标只能为整数或整数表达式。例如下面的表达形式都是正确的。

        shu[10]

        shu[i+j]     (int i,j)

        shu[i++]     (int i)

        i=shu[5]     (int i,shu[10])

【实例剖析】

 

 

【例3-1】数组的付值与调用

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int i;

    int shu[10];

    for (i=0;i<10;i++)

      cin>>shu[i];

    for (i=0;i<10;i++)

      cout<<shu[i]<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

如【例3-1】所示,给数组“shu”付值时需要给它的一个指定数组元素付值,而调用数组“shu”时也同样必须调用它的一个指定元素,所以shu[i]的含义其实和定义个变量n,而调用变量n是没有太大区别的,只是在控制存储连续性上具有更多的优势。所以在处理大量数据,需要存储和调用时,一般都需要使用数组。但需要大家注意的是,因为main函数里定义的变量属于局部变量,所以其定义的变量多少要受到一定的显示,默认情况下是1M,以一个占4位大小的int类型举例:1M=1024*1024字节,int占4个字节,所以int变量不能够超过,1024*1024/4=262144个。所以在定义数组变量时,要注意数组的大小限制,或者定义全局变量(在后面章节有相关介绍)。

【例3-2】数组的初始化

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

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

    int zong=0,jun,i;

    for (i=0;i<5;i++)

    {

      zong=zong+shu[i];

    }

    jun=zong/5;

    cout<<jun<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

如【例3-2】所示,数组和普通变量一样(如int zong=0),也可以在定义时进行初始化工作,格式即为:数组类型数组名称[数组元素数]={数组元素0初始值,数组元素1初始值,……}。

 

 

【尝试探索】

 

1、桃桃摘苹果

陶家的院子里有一棵苹果树,每到秋天树上就会结出10个苹果。苹果成熟的时候,陶陶就会跑去摘苹果。陶陶有个30厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩到板凳上再试试。

现在已知10个苹果到地面的高度,以及陶陶把手伸直的时候能够达到的最大高度,请帮陶陶算一下她能够摘到的苹果的数目。假设她碰到苹果,苹果就会掉下来。

 

2、斐波纳契数列:请尝试输出数列11235813,…

3、约瑟夫问题:有N个人坐成一圈,从1开始报数,报到m。报m的人出列,求出列的顺序,如4个人报3,顺序为:3 2 4 1

 

【拓展提高】

 

在前面的内容中,介绍了数组的最简单使用,但是在数组的使用上有很多变化性的内容需要处理。将数组的环形化处理是数组变化的主要形式之一。很多经典的习题也都是围绕着这个知识点展开的。下面介绍两个经典的例题:

 

【例3-3】 围坐成一圈的12个小朋友,每个人身上都有若干个苹果,老师要选出哪5个挨着做的小朋友手中的苹果总数最多,你能帮助老师们编写一个程序来确定吗?

 

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int shu[16];

    int i,max=0,wei=0;

    for (i=0;i<12;i++)

    {

      cin>>shu[i];

    }

    shu[12]=shu[0];

    shu[13]=shu[1];

    shu[14]=shu[2];

    shu[15]=shu[3];

    max=shu[0]+shu[1]+shu[2]+shu[3]+shu[4];

    for (i=0;i<12;i++)

    {

        if ((shu[i]+shu[i+1]+shu[i+2]+shu[i+3]+shu[i+4])>max)

        {

          max=shu[i]+shu[i+1]+shu[i+2]+shu[i+3]+shu[i+4];

          wei=i;

        }

    }

    cout<<"从第"<<wei+1<<"位学生开始最多,总共为"<<max<<"个苹果";

    system("PAUSE");

    return EXIT_SUCCESS;

}

 

从例题中可以看出,好象没有处理环的问题,其实这其中是使用了一个技巧。用延长了的线性结构代替了环型结构。在程序中,人工的将shu[12],shu[13] ,shu[14 ],shu[15] 变成了shu[0],shu[1],shu[2],shu[3]。这样在计算从shu[11]开始的连续五个数时,就不用特殊处理后面的数,相应的就把程序简化了。

 

【例3-4】杨辉三角与二维数组

1

1 1

1 2 1

1 3 3 1

……

上面就是所说的杨辉三角形。从表面来看,这一行的值就是上一行对应的值和前面一个值之和。根据题意设计,实现程序如下:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int yh[100][100];

    int i,j;

    for (i=0;i<100;i++)

      for (j=0;j<100;j++)

        yh[i][j]=0;

    yh[0][1]=1;

    for (i=1;i<100;i++)

      for (j=1;j<100;j++)

        yh[i][j]=yh[i-1][j-1]+yh[i-1][j];

    for (i=0;i<10;i++)

    {

      for (j=1;j<=i+1;j++)

        cout<<yh[i][j]<<" ";

      cout<<endl;

    }

    system("PAUSE");

    return EXIT_SUCCESS;

}

在程序中,定义了一个数组:int yh[100][100],这就是二维数组,只是在形式上比一维数组多了一个维度,在赋值和调用时,都没有什么太大的差异。

第二节 简单排序算法

【学习目标】

l         了解排序算法的现实需求

l         掌握标准选择法排序

l        掌握标准法冒泡排序

【知识准备】

数组的基础知识是数组应用的前提条件,在熟悉了数组的使用方法之后,可以使用数组来完成非常多的应用。在这些应用中,最重要的就是排序。

排序即是把若干无规则排列的数或值按照一定的规则,重新排列为新的有序的数或值的过程。经过很多年的推敲和演变,形成了很多的排序算法。选择法和冒泡法排序就是其中的典型代表。

 

【实例剖析】

 

【例3-5】选择法排序

    原理分析:

选择法排序的原理非常简单,以6个数为例,过程如下图所示:

图3-1

开始时,若干没有排序的数字输入到数组中,分别存在a[0],a[1]…a[5]中,比较法排序的原理就是,拿a[0]中的数和后面所有的数进行比较,发现比第一个数大的就进行交换,在一轮结束就会挑出最大的数存放在第一位中。然后,拿a[1]中的数和它后面的数进行比较,这样在第二轮结束后,就能够第二大的数存放在a[1]中,经过N轮的比较,就可以把这N个数以从大到小的顺序存储在数组中了。

实现程序:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int i,j,temp;

    int a[6];

    for (i=0;i<6;i++)

      cin>>a[i];

    for (i=0;i<5;i++)

      for (j=i+1;j<6;j++)

        if (a[i]<a[j])

        {

          temp=a[i];

          a[i]=a[j];

          a[j]=temp;

        }

    for (i=0;i<6;i++)

      cout<<a[i]<<" ";

    cout<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

 

【例3-6】冒泡法排序

原理分析:

冒泡法排序的基本思路就是:依次比较相临的两个数,将大数放在前面,小数放在后面。即从a[0]开始和a[1]比较,将大数放在前面的a[0]里,将小数放在后面的a[1]里。然后再将a[1]和a[2]比较,将大数放在前面的a[1]里,将小数放在后面的a[2]里。如此继续,直到比较到最后两个数时,将大数放在前面,将小数放在后面,此时内循环结束一次,此时最后的数必定是整个数组中的最小数。重复上面的过程,仍从第一个数a[0]开始比较,重复至最后一个数的前面一对相邻数,将大数放前,小数放后,内循环再次结束,在倒数第二个数中一定存储着整个数组中的第二小数。如此重复,直到最后一趟,只比较第一对数,将大数放前,小数放后,从而最终完成排序。

由于在排序过程中总是大数(类似于大气泡)往前放,小数(类似小气泡)往后放,就像水中的气泡一样,所以得名冒炮法排序。

仍以6个数为例,冒泡法排序的过程如下图所示:

图3-2

 

实现程序:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int i,j,temp;

    int a[6];

    for (i=0;i<6;i++)

      cin>>a[i];

    for (i=0;i<5;i++)

      for (j=0;j<6-i-1;j++)

        if(a[j]<a[j+1])

        {

          temp=a[j];

          a[j]=a[j+1];

          a[j+1]=temp;

        }

    for (i=0;i<6;i++)

      cout<<a[i]<<" ";

    cout<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

 

【尝试探索】

1、程序设计课进行了一次测试,一共有6名同学参加这次测试。请编写程序将这6名同学的成绩进行排序并输出。在输入时,依次输入这6名同学的学号和成绩。输出时按照由大到小的顺序输出学号和对应学号的学生成绩(如相同依输入顺序输出)。

如输入:

1  60

2  80

3  100

4  40

5  70

6  80

则输出

3  100

2  80

6  80

5  70

1  60

4  40

 

【拓展提高】

    【例3-7】优化的选择法排序

在掌握了选择法排序后,那么思考一下如何对选择法进行改进以提高它的运行效率呢?看一下选择法的运行过程,由于每次交换两个元素需要执行3个语句,过多的交换必定要花费许多时间。改进方案是在内循环的比较中找出最大值元素的下标,在内循环结束后再考虑是否要交换,程序如下:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int i,j,k,temp,d;

    int a[6];

    for (i=0;i<6;i++)

      cin>>a[i];

    for (i=0;i<6;i++)

    {

        d=i;

        for (j=i+1;j<6;j++)

        {

            if (a[d]>a[j])

              d=j;

        }

        if (d!=i)

        {

          temp=a[i];

          a[i]=a[d];

          a[d]=temp;

        }

        cout<<a[i]<<" ";

    }

   

    system("PAUSE");

    return EXIT_SUCCESS;

}

【例3-8】优化的冒泡法排序

对于冒泡法,在很多的情况下,在经过几次交换后,数组已经被排列好了,如上面的6个数的排序为例,在第二轮结束时,序列就已经排序完成了,但程序仍将进行下一轮的比较,不管在下一轮中是否会发生交换事件,程序都将会把比较进行到结束为止。而实际上第三轮中并为发生任何的交换事件,应该可以认为排序已经结束。依照这样的情况,可以对冒泡法做相应的优化。

为了标志在比较中是否进行了数据的交换,可以设定一个变量对交换进行记次,此变量每轮都恢复初值为0,在这轮比较中每发生一次比较就对这个变量做加1的操作。在每轮结束后判断这个值,如果它仍为零,代表没有发生交换,则可以认为排序已经结束。

实现程序如下:

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    int a[6];

    int i,j,temp,huan;

    for (i=0;i<6;i++)

      cin>>a[i];

    for (i=0;i<5;i++)

    {

      huan=0;

      for (j=0;j<6-1-i;j++)

      {

        if (a[j]<a[j+1])

        {

          huan++;

          temp=a[j];

          a[j]=a[j+1];

          a[j+1]=temp;     

                  

        }

      }

      if (huan==0)

        break;

    }

    for (i=0;i<6;i++)

      cout<<a[i]<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

 

第三节 字符数组

【学习目标】

l         了解字符数组的定义方法和使用规则

l         了解字符数组的特定结束要求

l         熟练掌握字符数组的各种操作

l         理解字符数组的应用情况和方法

【知识准备】

    字符数组(又称字符串)其实与其他数组没有什么本质上的差别,它是由字符类型的元素组成的。在程序设计中,字符数组有着很大的应用,为了方便程序员的编程,在各种语言中都针对字符数组(字符串)提供了相关的函数。熟练的掌握这类函数和字符数组特定的操作方式,对程序设计来说是非常必要的。

【实例剖析】

【例3-9】字符数组的输入及输出

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    char s1[10];

    char s2[10];

    cin>>s1;

    cin>>s2;

    cout<<s1<<endl;

    cout<<s2<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

    如上面程序所示,字符串数组的输入和输出似乎跟得到一个整数没有什么区别,而其实程序内部存储和编译的原理都大相径庭。在定义整数型数组的时候,可能没有人会考虑int a[10]中的数组名称a到底是什么,因为在程序调用的时候,使用的是数组中特定的a[0],a[1]……而没有使用过a+1这样的形式。但是在字符数组中,我们输出整个数组时,是可以写cout<<s1<<endl;的。那么就必须要求程序员在这里明白,到底s1是什么?

图3-3

如图3-3中所示,s1其实就是我们这个数组在内存中的起始地址,而不是一个跟内容有关的特定的数或者值。而C/C++中,为了标记这个字符数组的终止定义了一个特定的结束标志符“\0”。这是一个非常重要的标志,在很多的时候我们可能要人为的使用“\0”去干预字符数组的结束以达到程序设计的要求。和整数数组类似,在图3-3中还可以看出s1[0]就代表值‘c’,它本身是一个字符类型的值。

根据字符数组的存储原理,如果在程序中需要对s1和s2两个数组进行连接、比较、复制等一系列操作的时候,就不能够简单使用if (s1==s2)或者s1=s1+s2这样的语句来进行。

【例3-10】字符数组的基本操作

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    char s1[30],s2[30],s3[30];

    int i,n1,n2,n3;

    cin>>s1;

    cin>>s2;

    n1=strlen(s1);

    n2=strlen(s2);

    strcpy(s3,s1);

    strcat(s3,s2);

    n3=strlen(s3);

    cout<<s1<<" length="<<n1<<endl;

    cout<<s2<<" length="<<n2<<endl;

    cout<<s3<<" length="<<n3<<endl;

    if (strcmp(s1,s2)>0)

      cout<<"big";

    if (strcmp(s1,s2)<0)

      cout<<"small";

    if (strcmp(s1,s2)==0)

      cout<<"same";

    system("PAUSE");

    return EXIT_SUCCESS;

}

【例3-10】中对字符数组的操作是比较全面的。其中涉及到几个关于字符串的函数需要解释一下:

测长函数:strlen(字符数组),这个函数将返回字符串的实际长度。

连接函数:strcat(字符数组1,字符数组2),这个函数将把字符数组2接到字符数组1的后面,结果放在字符数组1中。这个函数将会返回字符数组1的地址。

复制函数:strcpy(字符数组1,字符数组2),这个函数将字符数组2复制到字符数组1中。

说明:

(1)字符数组1必须定义的足够大,以便容纳被复制的字符数组;

(2)字符数组1必须写成数组名形式(如s1),字符数组2可以是字符数组名也可以是一个字符串常量。如strcpy(s1,"China");

(3)复制时连同字符串后面的'\0'一起复制到字符数组1中;

(4)可以用strcpy函数将字符串2中前面若干个字符复制到字符数组1中。例如:strcpy(s1,s2,2);其作用是将s2中前2个字符复制到s1中,然后再加一个'\0'。

比较函数:strcmp(字符数组1,字符数组2),作用是比较字符数组1和字符数组2。

   

例如:

          strcmp(s1,s2);

          strcmp("China","America");

          strcmp(s1,"bayi");

    说明:

     字符数组的比较规则与其它语言中的规则相同,即对两个字符数组自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到'\0'为止。如全部字符相同,则认为相等;若出现不同的字符,则以第一个不同字符的比较结果为准。例如:"B"<"C","b">"B","computer">"compare","123"<"321"等等。

          比较的结果由函数值带回。

          (1)如果字符数组1=字符数组2,函数值为0;

          (2)如果字符数组1 >字符数组2,函数值为一正整数;

          (3)如果字符数组1 <字符数组2,函数值为一负整数。

      所以正如上面提到过的两个字符数组比较,不能使用:if (s1==s2)这样的语句,而必须使用:if (strcmp(s1,s2)==0)这样的语句。

      其实在例题中没有出现而比较有用的函数还有:

转换小写函数:strlwr(字符数组),将字符数组中的所有大写字母转换成小写;

转换答谢函数:strupr(字符数组),将字符数组中的所有小写字母转换成大写。

【尝试探索】

1、单词加密

从键盘任意得到一个单词,通过加密,将它变成另外一个单词。加密方法为:将字母转变为其后面的第5个字母,如a变为f,b变为g……y变为d,z变为e,A变成F,B变成G……Y变成D,Z变成E。然后输出。

2、比较大数

有两个50位以内的正整数,请比较他们的大小。提示:分为不同长度和相同长度比较。

3、依次删掉字符数组中的最后一个字符然后输出。

如,字符数组为abcd,则输出

abcd

abc

ab

a

【拓展提高】

【例3-11】统计单词数

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    char s[100];

    int l,i;

    int n=0,word=0;

    gets(s);

    l=strlen(s);

    for (i=0;i<l;i++)

      if (s[i]==' ')

        word=0;

      else

        if (word==0)

        {

          word=1;

          n++;

        }

    cout<<n<<" words in this line"<<endl;     

    system("PAUSE");

    return EXIT_SUCCESS;

}

    通过前面的学习,在得到字符串时,我们一般使用的是cin语句。但是cin语句是以非可视字符表示结束,但是有的时候我们需要得到的是一个包含空格的句子时,使用cin语句就不合适了,这个时候我们需要使用的就是gets(字符数组);语句,然后再按照需求对字符数组进行分析。相应的,如果我们需要输出的是一个含空格(或其他非可视字符)的句子时,也需要使用puts(字符数组);语句来完成。

【例3-12】字符数组的地址操作

#include <cstdlib>

#include <iostream>

 

using namespace std;

 

int main(int argc, char *argv[])

{

    char s[10];

    int i,l;

    cin>>s;

    l=strlen(s);

    for (i=0;i<l;i++)

      cout<<s+i<<endl;

    system("PAUSE");

    return EXIT_SUCCESS;

}

    为了更好的利用一下,s是字符数组的内存地址这个条件,我们可以用上述方法,依次删除字符数组的首字母输出。

 

发布了29 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/fatship/article/details/85244467