《 C++语言程序设计》课程辅导二

------数组和字符串

一、数组的概念

在程序设计中存储单个数据时,需要根据数据的类型定义相应的变量来保存。如存储一个整数时需要定义一个整数变量来保存,存储一个实数时需要定义一个单精度或双精度变量来保存,存储含有多个成分的一个记录数据时,需要定义该类型的一个结构变量来保存。

若在程序设计中需要存储同一数据类型的、彼此相关的多个数据时,如存储数学上使用的一个数列或一个矩阵中的全部数据时,显然采用定义简单变量的方法是不行的,这就要求定义出能够同时存储多个值的变量,这种变量在程序设计中称为 数组

在实际应用中,一组相关的数据之间可能存在着一维关系,也可能存在着二维关系,等等。一个数列中的数据若是一维关系,则它除第一个数据外,每个数据只有一个直接前驱;除最后一个数据外,每个数据只有一个直接后继。假定一个数列为(38,42,25,60),则每个数的后一个数就是它的直接后继,每一个数的前一个数就是它的直接前驱,如42的直接前驱为38,直接后继为25。一个矩阵中的数据若二维关系,则它除第一行和第一列上的所有数据外,每个数据在行和列的方向上各有一个直接前驱;除最后一行和最后一列上的所有数据外,每个数据在行和列的方向上各有一个直接后继。假定一个矩阵为:

2 6 9 12

8 4 7 3

5 1 6 8

则每一个元素均处于相应行和列的交点位置上,虽然有的元素值相同,但由于所处的位置不同,所以是不同的元素。

在程序设计中,用一维数组表示和存储一维相关的数据,用二维数组表示和存储二维相关的数据,用三维数组表示和存储三维相关的数据,等等。假定一个数列为a 1 ,a 2 ,...,a n ,则需要用一个一维数组来存储,假定仍用a作为数组名,则a中应至少包含有n个元素,每个元素用来存储数列中一个相应的数据。若a中正好包含有n个元素,则这n个元素依次表示为a[0],a[1],...,a[n-1],用a[0]存储数列中的第一个数据a 1 ,用a[1]存储数列中的第二个数据a 2 ,依次类推。假定一个矩阵为:

a 11 a 12 ... a 1n

a 21 a 22 ... a 2n

M M ... M

a m1 a m2 ... a mn

则需要用一个二维数组来存储,假定二维数组名用 b表示,则b中应至少包含m ′ n个元素,也就是说,第一维尺寸至少为m,第二维尺寸至少为n,该数组b中的每个元素用来存储矩阵中的一个相应的数据。

二、数组的定义

2.1 一维数组

1. 定义格式

一维数组同简单变量一样,也是通过变量定义语句定义的。其定义格式为:

<类型关键字> <数组名> [<常量表达式>] [={<初值表>}];

<类型关键字>为已存在的一种数据类型,<数组名>是用户定义的一个标识符,用它来表示一个数组,<常量表达式>的值是一个整数,由它标明该数组的长度,即数组中所含元素的个数,每个元素具有<类型关键字>所指定的类型,<常量表达式>两边的中括号是语法所要求的符号,不是标明其内容为可选而使用的符号,<初值表>是用逗号分开的一组表达式,每个表达式的值将被赋给数组中的相应元素。

当数组定义中包含有初始化选项时,其<常量表达式>可以被省略,此时所定义的数组的长度将是<初值表>中所含的表达式的个数。

一个数组被定义后,系统将在内存中为它分配一块含有n个(n为数组长度)存储单元的存储空间,每个存储单元包含的字节数等于元素类型的长度。如对于一个含有10个int型元素的数组,它将对应10*4=40个字节的存储空间。

定义了一个数组,就相当于同时定义了它所含的每个元素。数组中的每个元素是通过下标运算符[]来指明和访问的,具体格式为:“<数组名>[<下标>]”,这与数组的定义格式相同,但出现的位置是不同的,当出现在变量定义语句时则为数组定义,而当出现在表达式中时则为一个元素。

对于一个含有n个元素的数组,C++语言规定:它的下标依次为0,1,2,...,n-1,因此全部n个元素依次为a[0],a[1],a[2],...,a[n-1],其中假定a为数组名。

2. 格式举例

(1) int a[20];

(2) double b[MS]; //假定MS为已定义的整型常量

(3) int c[5]={1,2,3,4,0};

(4) char d[]={'a','b','c','d'};

(5) int e[8]={1,4,7};

(6) char f[10]={'B','A','S','I','C'};

(7) bool g[2*N+1]; //假定N为已定义的整型常量

(8) float h1[5], h2[10];

(9) short x=1, y=2, z, w[4]={25+x, -10, x+2*y, 44};

(10) int p[];

第一条语句定义了一个元素为int型、数组名为a、包含20个元素的数组,所含元素依次为a[0],a[1],...,a[19],每个元素同一个int型简单变量一样,占用4个字节的存储空间,用来存储一个整数,整个数组占用80个字节的存储空间,用来存储20个整数。

第二条语句定义了一个元素类型为double、数组长度为MS的数组b,该数组占用MS*8个字节的存储空间,能够用来存储MS个双精度数,数组b中的元素依次为b[0],b[1],...,b[MS-1]。

第三条语句定义了一个整型数组c,即元素类型为整型的数组c,它的长度为5,所含元素依次为c[0],c[1],c[2],c[3]和c[4],并相应被初始化为1,2,3,4和0。

第四条语句定义了一个字符数组d,由于没有显式地给出它的长度,所以隐含为初值表中表达式的个数4,该数组的4个元素d[0],d[1],d[2]和d[3]依次被初始化为 ' a ' , ' b ' , ' c ' ,和 ' d ' 。注意若没有给出数组的初始化选项,则表示数组长度的常量表达式不能省略。

第五条语句定义了一个含有8个元素的整型数组e,它的初始化数据项的个数为3,小于数组中元素的个数8,这是允许的,这种情况的初始化过程为:将利用初始化表对前面相应元素进行初始化,而对后面剩余的元素则自动初始化为常数0。数组e中的8个元素被初始化后得到的结果为:e[0]=1, e[1]=4, e[2]=7, e[3] ~ e[7]=0。

第六条语句定义了一个字符数组f,它包含有10个字符元素,其中前5个元素被初始化为初值表所给的相应值,后5个元素被初始化为字符 ' \0 ' ,对应数值为 0。

第七条语句定义了一个布尔型数组g,它的数组长度为2*N+1,每个元素没有被初始化。

第八条语句定义了两个单精度型一维数组h1和h2,它们的数组长度分别为5和10。在一条变量定义语句中,可以同时定义任意多个简单变量和数组,每两个相邻定义项之间必须用逗号分开。

第九条语句定义了三个短整型简单变量x,y和z,其中x和y被初始化为1和2,定义了一个短整型数组w,它包含有四个元素,其中w[0]被初始化为25+x的值,即26,w[1]被初始化为-10,w[2]被初始化为x+2*y的值,即5,w[3]被初始化为44。

第十条语句是错误的数组定义,因为它既省略了数组长度选项,又省略了初始化选项,使系统无法确定该数组的大小,从而无法分配给它确定的存储空间。

3. 数组元素的访问

通过变量定义语句定义了一个数组后,用户便可以随时使用其中的任何元素。数组元素的使用是通过下标运算符[]指明和访问的,其中运算符左边为数组名,中间为下标。一个数组元素又称为下标变量,所使用的下标可以为常量,也可以为变量或表达式,但其值必须是整数,否则将产生编译错误。

假定a[n]为一个已定义的数组,则下面都是访问该数组的下标变量的合法格式:

(1) a[5] //下标为一个常数

(2) a[i] //下标为一个变量

(3) a[j++] //下标为后增1表达式

(4) a[2*x+1] //下标为一般表达式

假定在上述每个变量的下标表达式中,所使用的变量i,j和x的值分别为2,3和4,则a[i]对应的数组元素为a[2],a[j++]对应的数组元素为a[3],同时j的值被修改为4,a[2*x+1]对应的数组元素为a[9]。

使用一个下标变量同使用一个简单变量一样,可以对它赋值,也可以取出它的值。如:

(1) int a[5]={0,1,2,3,8}; //定义数组a并进行初始化

(2) a[0]=4; //把4赋给a[0]

(3) a[1]+=a[0]; //把a[0]的值4累加到a[1],使a[1]的值变为5

(4) a[3]=3*a[2]+1; //把赋值号右边表达式的值7赋给a[3]

(5) cout<<a[a[0]]; //因a[0]=4,所以a[a[0]]对应的元素为a[4],

//该语句输出a[4]的值8

C++语言对数组元素的下标值不作任何检查,也就是说,当下标值超出它的有效变化范围0 ~ n-1(假定n为数组长度)时,也不会给出任何出错信息。为了防止下标值越界(即小于0或大于n-1),则需要编程者对下标值进行有效性检查。如:

(1) int a[5];

(2) for(int i=0; i<5; i++) a[i]=i*i;

(3) for(i=0; i<5; i++) cout<<a[i]<< ' ' ;

第一条语句定义了一个数组a,其长度为5,下标变化范围为0 ~ 4。第二条语句让循环变量i在数组a下标的有效范围内变化,使下标为i的元素赋值为i的平方值,该循环执行后数组元素a[0],a[1],a[2],a[3]和a[4]的值依次为0,1,4,9和16。第三条语句控制输出数组a中每一个元素的值,输出语句中下标变量a[i]中的下标i的值不会超出它的有效范围。如果在第三条语句中,用做循环判断条件的<表达式2>不是i<5,而是i<=5,则虽然a[5]不属于数组a的元素,但也同样会输出它的值,而从编程者角度来看是一种错误。由于C++系统不对元素的下标值进行有效性检查,所以用户必须通过程序检查,确保其下标值有效。

4. 程序举例

(1) #include<iostream.h>

void main()

{

int i, a[6];

for(i=0;i<6;i++) cin>>a[i];

for(i=5;i>=0;i--) cout<<a[i]<<' ';

cout<<endl;

}

在程序的主函数中首先定义了一个int型简单变量i和一个含有6个int型元素的数组a,接着使数组a中的每一个元素依次从键盘上得到一个相应的整数,最后使数组a中的每一个元素的值按下标从大到小的次序显示出来,每个值之后显示出一个空格,以便使相邻的元素值分开。

若程序运行时,从键盘上输入3,8,12,6,20,15这6个整数,则得到的输入和运行结果为:

3 8 12 6 20 15

15 20 6 12 8 3

(2) #include<iostream.h>

void main()

{

int a[8]={25,64,38,40,75,66,38,54};

int max=a[0];

for(int i=1;i<8;i++)

if(a[i]>max) max=a[i];

cout<<"max:"<<max<<endl;

}

在这个程序的主函数中,第一条语句定义了一个整型数组a[8],并对它进行了初始化;第二条语句定义了一个整型变量max,并用数组a中第一个元素a[0]的值初始化;第三条语句是一个for循环,它让循环变量i从1依次取值到7,依次使数组a中的每一个元素a[i]同max进行比较,若元素值大于max的值,则就把它赋给max,使max始终保存着从a[0] ~ a[i]元素之间的最大值,当循环结束后,max的值就是数组a中所有元素的最大值;第四条语句输出max的值。

在该程序的执行过程中,max依次取a[0],a[1]和a[4]的值,不会取其他元素的值。程序运行结果为:

max:75

(3) #include<iostream.h>

const int N=7;

void main()

{

double w[N]={2.6,7.3,4.2,5.4,6.2,3.8,1.4};

int i,x;

cout<<"输入一个实数:";

cin>>x;

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

if(w[i]>x) cout<<"w["<<i<<"]="<<w[i]<<endl;

}

此程序的功能是从数组a[N]中顺序查找出比x值大的所有元素并显示出来。若从键盘上输入的x值为5.0,则得到的程序运行结果为:

输入一个实数:5.0

w[1]=7.3

w[3]=5.4

w[4]=6.2

(4) #include<iostream.h>

const int M=10;

void main()

{

int a[M+1];

a[0]=1; a[1]=2;

int i;

for(i=2;i<=M;++i)

a[i]=a[i-1]+a[i-2];

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

cout<<a[i]<<',';

cout<<a[M]<<endl;

}

该程序首先定义数组a,并分别为数组元素a[0]和a[1]赋值1和2,接着依次计算出a[2]至a[M]的值,每个元素值均等于它的前两个元素值之和,最后按照下标从小到大的次序显示出数组a中每个元素的值。该程序运行结果为:

1,2,3,5,8,13,21,34,55,89,144

2.2 二维数组

1. 定义格式

二维数组同一维数组一样,也是通过变量定义语句定义的,其定义格式为:

<类型关键字> <数组名> [<常量表达式1>] [<常量表达式2>]

[={{<初值表1>},{<初值表2>},...}];

在上述定义格式中,<常量表达式1>和<常量表达式2>两边的中括号也同一维数组定义中<常量表达式>两边的中括号的用法相同,都是语法所要求的符号,不是指一般规定的其内容为任选项的标识。

二维数组定义中的<常量表达式1>和<常量表达式2>分别指定数组的第一维下标(又称为行下标)和第二维下标(又称为列下标)取值的个数,假定<常量表达式1>和<常量表达式2>的值分别为m和n,则行下标的取值范围是0 ~ m-1之间的m个整数,列下标的取值范围是0 ~ n-1之间的n个整数。

对于一个行下标取值个数为m,列下标取值个数为n的二维数组a,它所含元素的个数为m*n,即数组长度为m*n,每一个元素含有两个下标,具体表示为:“<数组名>[<行下标>][<列下标>]”,数组a中的所有元素表示为:

a[0][0] a[0][1] ... a[0][n-1]

a[1][0] a[1][1] ... a[1][n-1]

M M M M

a[m-1][0] a[m-1][1] ... a[m-1][n-1]

我们知道,当定义了一个一维数组后,系统为它分配一块连续的存储空间,该空间的大小为n*sizeof(<元素类型>),其中n为一维数组长度。

在C++系统中,数组名同时表示该数组占用的存储空间的首地址。例如,若定义了一个int型的一维数组b[10],则下标为i的元素b[i]的地址为b+4*i,其中0≤i≤9。在内存中数组b的存储分配示意图为:

0 1 2 3 4 5 6 7 8 9

b[0]

b[1]

b[2]

b[3]

b[4]

b[5]

b[6]

b[7]

b[8]

b[9]

0 4 8 12 16 20 24 28 32 36

其中每个方框表示一个元素的存储单元,它的上面为该元素的下标,也是存储单元的顺序编号,下面为该元素相对于首地址b的偏移地址。

当定义了一个二维数组后,系统也同样为它分配一块连续的存储空间,该存储空间的大小为m*n*sizeof(<元素类型>),其中m和n分别表示第一维下标和第二维下标的取值个数。

系统给一个二维数组中的所有元素分配存储单元时,是首先按行下标从小到大的次序,行下标相同再按列下标从小到大的次序进行的。例如,若定义了一个double型的二维数组c[M][N],则任一元素c[i][j]的地址为c+(i*N+j)*8,其中0≤i≤M-1, 0≤j≤N-1。假定常量M和N分别为4和2,则数组c的存储分配示意图为:

0 1 2 3 4 5 6 7

c[0][0]

c[0][1]

c[1][0]

c[1][1]

c[2][0]

C[2][1]

c[3][0]

c[3][1]

0 8 16 24 32 40 48 56

同一维数组的存储分配示意图一样,每个方框表示一个元素的存储单元,它的上面为存储单元的顺序编号,下面为该元素相对于首地址c的偏移地址。

若要计算c[2][1]的存储地址,则为c+(2*2+1)*8=c+40。

若在二维数组的定义格式中,包含有最后的初始化选项,则能够在定义二维数组的同时,对所有元素进行初始化,其中每个用花括号括起来的初值表用于初始化数组中的一行元素,即<初值表1>用于初始化行下标为0的所有元素,<初值表2>用于初始化行下标为1的所有元素,依次类推。同一维数组的初始化一样,若有的元素没有对应的初始化数据,则自动对它初始化为0。

在二维数组的定义格式中,若带有初始化选项,则<常量表达式1>可以省略,此时将定义一个行数等于初值表个数的二维数组。

2. 格式举例

(1) int a[3][3];

(2) double b[M][N]; //假定M和N为整型常量

(3) int c[2][4]={{1,3,5,7},{2,4,6,8}};

(4) int d[][3]={{0,1,2},{3,4,5},{6,7,8}};

(5) int e[3][4]={{0},{1,2}};

(6) char f[CN+1][CN+1],c1= ' a ' ,c2; //假定CN为整型常量

(7) int g[10],h[10][5];

(8) int r[][5];

第一条语句定义了一个二维数组a[3][3],它包含有9个元素,元素类型为int,每个元素同一个int型简单变量一样,能够用来表示和存储一个整数。

第二条语句定义了一个元素类型为double的二维数组b[M][N],它包含M*N个元素,每个元素用来保存一个实数,元素中行下标的有效范围为0 ~ M-1,列下标的有效范围为0 ~ N-1,任一元素b[i][j]的存储地址为b+(i*N+j)*8,当然i和j都要在有效取值范围以内。

第三条语句定义了一个元素类型为int的二维数组c[2][4],并对该数组进行了初始化,使得c[0][0], c[0][1], c[0][2]和c[0][3]的初值分别为1,3,5和7;c[1][0], c[1][1], c[1][2]和c[1][3]的初值分别为2,4,6和8。

第四条语句定义了一个元素类型为int的二维数组d,它的列下标的取值范围为0 ~ 2,行下标的取值范围没有显式给出,但由于给出了初始化选项,并且含有三个初值表,所以取值范围隐含为0 ~ 2,相当于在数组定义的第一个中括号内省略了行下标取值个数3。

第五条语句定义了一个元素类型为int的二维数组e[3][4],它的第1行(即行下标为0)的四个元素被初始化为0,第2行的四个元素e[1][0], e[1][1], e[1][2]和e[1][3]分别被初始化为1,2,0和0,第3行的四个元素也均被初始化为0。

第六条语句定义了一个元素类型为char的二维数组f,它的行、列下标的上界均为CN,其取值均为0 ~ CN之间的整数,该语句同时定义了字符变量c1和c2,并使c1初始化为字符 ' a ' 。

第七条语句同时定义了两个元素类型为int的数组,一个为一维数组g[10],另一个为二维数组h[10][5],它们分别含有10个元素和50个元素,每个元素能够表示和存储一个整数。

第八条语句定义的二维数组r是错误的,因为它既没有给出第一维下标的取值个数,又没有给出初始化选项,所以系统无法确定该数组的长度,从而无法为它分配一定大小的存储空间。

3. 数组元素的访问

一个二维数组被定义后,与使用一维数组一样,是通过下标运算符指明和访问元素,其中对行下标和列下标都要进行运算才能够唯一指定一个元素。二维数组中的一个元素由于使用了两个下标,所以又称为双下标变量。一个双下标变量中的任一个下标不仅可以为常量,同样可以为变量或表达式,当然它们都必须为整数类型。如:

(1) a[2][3] //每个下标均为常量

(2) a[i][j] //每个下标均为变量

(3) a[i][5] //行下标为变量,列下标为常数

(4) a[i-1][j+1] //每个下标均为表达式

若i和j的值分别为2和3,则上述下标变量a[i][j]对应的元素为a[2][3],a[i][5]对应的元素为a[2][5],a[i-1][j+1]对应的元素为a[1][4]。

使用双下标变量同使用单下标变量和简单变量一样,既可以用它存储数据,又可以取出它的值参加运算。如:

(1) int a[4][5]; //定义数组

(2) a[1][2]=6; //向a[1][2]元素赋值6

(3) a[2][2]=3*a[1][2]+1; //取出a[1][2]的值6参与运算,

//把赋值号右边表达式的值19赋给a[2][2]元素中

(4) a[i][j-1]=a[i][j]; //把a[i][j]的值赋给a[i][j-1]元素中

(5) cout<<a[1][2]*a[2][2]-3<<endl; //输出表达式的值111到显示窗口上

C++系统对待二维下标变量同样不作下标有效性检查,所以也需要编程者通过程序进行检查处理,避免下标越界的情况发生。

在C++语言中,不仅可以定义和使用一维数组和二维数组,也可以定义和使用三维及更

高维的数组。如,下面的语句定义了一个三维数组:

int s[P][M][N]; //假定P,M,N均为已定义的整型常量

该数组的数组名为s,第一维下标的取值范围为0 ~ P-1,第二维下标的取值范围为0 ~ M-1,第三维下标的取值范围为0 ~ N-1。该数组共包含P*M*N个int型的元素,共占用P*M*N*4个字节的存储空间。数组中的每个元素由三个下标唯一确定,如s[1][0][3]就是该数组中的一个元素(假定P,M和N分别大于等于2,1和4)。

若用一个三维数组来表示一本书,则第一维表示页,第二维表示页内的行,第三维表示行内一个字符位置所在的列,数组中每个元素的值就是相应位置上的字符。

4. 程序举例

(1) #include<iomanip.h>

const int M=3,N=4;

void main()

{

int a[M][N]={{7,5,14,3},{6,20,7,8},{14,6,9,18}};

int i,j;

for(i=0;i<M;i++) {

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

cout<<setw(5)<<a[i][j];

cout<<endl;

}

}

该程序首先定义了一个元素为int类型的二维数组a[M][N],并对它进行了初始化;接着通过双重for循环输出每一个元素的值,其中外循环变量i控制行下标从小到大依次变化,内循环变量j控制列下标从小到大依次变化,每输出一个元素值占用显示窗口的5个字符宽度,当同一行元素(即行下标值相同的元素)输出完毕后,将输出一个换行符,以便下一行元素从显示窗口的下一行显示出来。该程序的运行结果为:

7 5 14 3

6 20 7 8

14 6 9 18

(2) #include<iostream.h>

void main()

{

int b[2][5]={{7,15,2,8,20},{12,25,37,16,28}};

int i,j,k=b[0][0];

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

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

if(b[i][j]>k) k=b[i][j];

cout<<k<<endl;

}

在这个程序中首先定义了元素类型为int的二维数组b[2][5]并初始化,接着定义了int型的简单变量i,j,k,并对k初始化为b[0][0]的值7,然后使用双重for循环依次访问数组b中的每个元素,并且每次把大于k的元素值赋给k,循环结束后k中将保存着所有元素的最大值,并被输出出来,这个值就是b[1][2]的值37。

(3) #include<iostream.h>

const int M=4;

void main()

{

int c[M]={0};

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

int i,j,sum=0;

for(i=0;i<M;i++) {

for(j=0;j<3;j++) c[i]+=d[i][j];

sum+=c[i];

}

for(i=0;i<M;i++) cout<<c[i]<<' ';

cout<<sum<<endl;

}

该程序主函数中的第一条语句定义了一个一维数组c[M]并使每个元素初始化为0,第二条语句定义了一个二维数组d[M][3]并使每个元素按所给的数值初始化,第三条语句定义了i,j和sum,并使sum初始化为0,第四条语句是一个双重for循环,它依次访问数组d中的每个元素,并把每个元素的值累加到数组c中与该元素的行下标值相同的对应元素中,然后再把数组c中的这个元素值累加到sum变量中,第五条语句依次输出数组c中的每个元素值,第六条语句输出sum的值。该程序把二维数组d中的同一行元素值累加到一维数组c中的相应元素中,把所有元素的值累加到简单变量sum中。该程序的运行结果为:

13 15 22 14 64

2.3 使用typedef语句定义数组类型

1. 一维数组类型的定义格式

typedef <元素类型关键字><数组类型名>[<常量表达式>];

例如:

(1) typedef int vector[10];

(2) typedef char strings[80];

(3) typedef short int array[N];

第一条语句定义了一个元素类型为int,含有10个元素的数组类型vector,若不使用typedef保留字,则就变成了数组定义,它只定义了一个元素类型为int、含有10个元素的数组vector。这两种定义有着本质的区别,若定义的是数组vector,系统将为它分配有保存10个整数的存储单元,共40个字节的存储空间;若定义的是数组类型vector,系统只是把该类型的有关信息登记下来,待以后利用该类型定义对象时使用,具体地说,就是把vector的元素类型int,类型长度10,类型名vectoe等登记下来,待以后定义vector类型的对象时使用。

第二条语句定义了一个元素类型为char,含有80个元素的数组类型strings,以后可以直接使用strings类型定义数组对象,每个数组对象的元素为char型,数组长度(即元素个数)为80。

第三条语句定义了一个元素类型为short int的含有N个元素(N为已定义的符号常量)的数组类型array,以后利用它可以直接定义该类型的对象,它是一个含有N个短整型元素的数组。

下面是利用上述类型定义对象的一些例子。

(1) vector v1,v2;

(2) strings s1,s2="define type";

(3) array a={25,36,19,48,44,50}; //假定常量N≥6

第一条语句定义了vector类型的两个对象v1和v2,每个对象都是vector类型的一个数组,每个数组由10个整型元素所组成。

第二条语句定义了strings类型的三个对象s1,s2和s3,并且对s3进行了初始化,每个对象都是含有80个字符空间的数组。

第三条语句定义了一个array类型的对象a,它是一个含有N个短整型元素的数组,该语句同时对数组a进行了初始化,使得a[0] ~ a[5]的元素值依次为25,36,19,48,44和50。

2. 二维数组类型的定义格式

typedef <元素类型关键字><数组类型名>[<常量表达式1>][<常量表达式2>];

例如:

(1) typedef int matrix[5][5];

(2) typedef char nameTable[10][NN];

(3) typedef double DD[M+1][N+1];

第一条语句定义了含有5行5列共25个int型元素的数组类型matrix,第二条语句定义了10行NN列共10*NN个char型元素的数组类型nameTable,第三条语句定义了含有M+1行N+1列共(M+1)*(N+1)个double类型元素的数组类型DD。

利用这三个二维数组类型可以直接定义出相应的二维数组。如:

(1) matrix mx={{0}};

(2) nameTable nt={""}; //或使用等同的{{'\0'}}初始化

(3) DD dd={{0.0}};

第一条语句定义了二维整型数组类型matrix的一个对象mx,该对象是一个5*5的二维整型数组,每个元素均被初始化为0;第二条语句定义了二维字符数组类型nameTable 的一个二维字符数组nt,该数组中的每个元素均被初始化为空字符;第三条语句定义了二维双精度数组类型DD的一个数组dd,它的每个元素均被初始化为0.0。

在typedef语句中,<元素类型关键字>可以是C++语言中预定义的任何一种数据类型,也可以是用户在前面已定义的任何一种数据类型,所以通过该语句定义的类型同样可以用在其后的typedef语句中。如:

(1) typedef vector vectorSet[20];

(2) vectorSet vs;

第一条语句定义了元素类型为vector,元素个数为20的一个数组类型vectorSet,第二条语句定义了数据类型为vectorSet的一个对象vs,该对象包含有20个类型为vector的元素,每个元素又包含有10个int类型的元素,所以整个数组共包含有20行10列共200个整数元素,它等同于对vs的如下定义:

int vs[20][10];

利用typedef语句同样可以定义更高维的数组类型,这里就不进行讨论了。

3. 对已有类型定义别名

利用typedef语句不仅能够定义数组类型,而且能够对已有类型定义出另一个类型名,以此作为原类型的一个别名。如:

(1) typedef int inData;

(2) typedef char chData;

(3) typedef char* chPointer;

第一条语句对int类型定义了一个别名inData,第二条语句对char类型定义了一个别名chData,第三条语句对char*类型(它是字符指针类型)定义了一个别名chPointer。以后使用inData,chData和chPointer就如同分别使用int,char和char*一样,定义出相应的对象。如:

(1) inData x,y;

(2) inData a[5]={1,2,3,4,5};

(3) chData b1,b2='a';

(4) chData c[10]="char data";

(5) chPointer p=0;

第一条语句定义了inData(即int)型的两个变量x和y,第二条语句定义了元素类型为int的一维数组a[5]并进行了初始化,第三条语句定义了chData(即char)型的两个变量b1和b2,并把b2初始化为 ' a ' , 第四条语句定义了一个字符数组 c[10]并初始化为 ” char data ” , 第五条语句定义了一个字符指针变量 p,并初始化为0(即NULL)。

三、数组的应用

数组是表示和存储数据的一种重要方法,利用数组能够进行计算、统计、排序、查找等各种运算。下面通过程序设计的例子来说明这些运算。

3.1 数值计算

例1.国家对个人月收入征收个人所得税的办法如表1-1所示,编一程序,根据一个人的月收入计算出应缴纳的税额和税后所得的金额。

表1-1 个人月收入所得税表

级 数

级 距

税 率(%)

1

800元以下部分

0

2

800 ~ 1500元之间部分

5

3

1500 ~ 3000元之间部分

10

4

3000 ~ 6000元之间部分

20

5

6000 ~ 9000元之间部分

30

6

9000 ~ 12000元之间部分

40

7

12000元以上部分

45

分析:由每一级的级距上界组成一个数列(最后一级的上界理论上为无穷大,但计算机无法表示一个无穷大的数,所以可用一个非常大的数,如1e9来表示),假定该数列用a表示;由每一级税率组成另一个数列,假定该数列用b表示,则a和b分别为:

a=(800,1500,3000,6000,9000,12000,1e9)

b=(0,0.05,0.10,0.20,0.30,0.40,0.45)

设用x表示一个人的月收入,用i表示x所对应的级数,用y表示月收入为x应缴纳的税额,则y的计算公式为:

y=(x-a i-1 )*b i +

其中1≤i≤7,a 1 ~ a 7 依次为数列a中对应的级距上界,b 1 ~ b 7 依次为数列b中对应的税率。如当x=4500时,对应的级距为4,应缴纳税额为:

y=(x-a 3 )*b 4 +

=(4500-3000)*0.20+(a 3 -a 2 )*b 3 +(a 2 -a 1 )*b 2

=300+(3000-1500)*0.10+(1500-800)*0.05

=300+150+35

=485

在编写此题的程序时,应首先说明存储数列a和b的两个一维数组,假定仍用标识符a和b表示,它们的长度应均为8,其中用a[i]和b[i]分别存储a i 和b i ,下标为0的元素未用;接着给x输入一个值,并求出它对应的级数i;最后计算出y的值,并打印出y和x-y的值,它们分别为上缴税额和税后所得的金额。

根据分析,编写出程序如下:

#include<iostream.h>

const int N=8;

void main()

{

double a[N]={0,800,1500,3000,6000,9000,12000,1e9};

double b[N]={0,0,0.05,0.10,0.20,0.30,0.40,0.45};

double x,y;

cout<<"输入一个人的月收入(单位\"元\"):";

cin>>x;

int i,j;

for(i=1;i<N;i++) if(x<=a[i]) break;

y=(x-a[i-1])*b[i];

for(j=i-1;j>=2;j--) y+=(a[j]-a[j-1])*b[j];

cout<<"所得税:"<<y<<endl;

cout<<"税后额:"<<x-y<<endl;

}

假定程序运行时,从键盘上输入8000作为x的值,则得到的运行结果为:

输入一个人的月收入(单位"元"):8000

所得税:1385

税后额:6615

例2. 已知两个矩阵A和B如下,编一程序计算出它们的和。

7 -5 3 3 6 -9

A= 2 8 -6 B= 2 -8 3

1 -4 -2 5 -2 -7

分析:由数学知识可知,行数和列数分别对应相同的两个矩阵可以做加法,它们的和仍为一个矩阵,并且与两个加数矩阵具有相同的行数和列数。此题中的两个矩阵均为3行 ′ 3列,所以它们的和矩阵同样为3行 ′ 3列。两矩阵加法运算的规则是:和矩阵中每个元素的值等于两个加数矩阵中对应位置上的元素值之和,即C ij =A ij +B ij ,其中A和B表示两个加数矩阵,C表示它们的和,即和矩阵。在程序中,首先应定义三个二维数组,假定分别用标识符a,b和c表示,分别对应A,B和C这三个矩阵,并需要对a和b进行初始化;接着根据a和b计算出c;然后按照矩阵的书写格式输出数组c,它就是对应的矩阵C。

根据分析编写出程序如下:

#include<iomanip.h>

const int N=3;

void main()

{

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

int b[N][N]={{3,6,-9},{2,-8,3},{5,-2,-7}};

int i,j,c[N][N];

for(i=0;i<N;i++) //计算矩阵C

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

c[i][j]=a[i][j]+b[i][j];

for(i=0;i<N;i++) { //输出矩阵C

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

cout<<setw(5)<<c[i][j];

cout<<endl;

}

}

该程序运行时得到的输出结果如下:

10 1 -6

4 0 -3

6 -6 -9

3.2 统计

例1. 有一个协会在换届选举中由全体会员无记名投票直选主席的活动,共有5名候选人,每个人的代号分别用1,2,3,4,5表示,每名会员填写一张选票,若同意某名候选人则在其姓名后打上对号即可,编一程序根据所有选票统计出每位候选人所得票数,其中每张选票上所投候选人的代号由键盘输入,当输入完所有选票后用-1作为终止数据输入的标志。

分析:由于需要分别统计5位候选人的票数,所以要同时使用5个统计变量,为此定义一个具有6个元素的一维整型数组,其中用下标为1的元素统计代号为1的候选人票数,用下标为2的元素统计代号为2的候选人票数,依次类推,而下标为0的元素未用。假定该数组为a[6],则当从键盘上输入的一个代号为1时,就在元素a[1]上加1,为2时就在元素a[2]上加1,总之当输入的代号为i(1≤i≤5)时就在a[i]上加1。当统计结束后每个数组元素a[i]的值就是代号为i的候选人最后所得的票数。

根据分析,编写出程序如下:

#include<iostream.h>

void main()

{

int i,a[6]={0};

cout<<"请依次输入每张选票上所投候选人的代号:";

cin>>i;

while(i!=-1) {

if(i>=1 && i<=5) a[i]++;

cin>>i;

}

cout<<endl;

for(i=1;i<=5;i++) cout<<i<<':'<<a[i]<<endl;

}

下面是程序一次运行的结果:

请依次输入每张选票上所投候选人的代号:1 2 3 2 4 3 5 1 3 4 2 3 5 -1

1:2

2:3

3:4

4:2

5:2

例2. 某社区对所属的N户居民进行用电量统计,每隔50度用电量为一个统计区间,但当大于等于500度时为一个统计区间,编一程序,分别统计每个用电区间内的居民户数。

分析:由题意可知,用电区间共有11个,其中0 ~ 49为第一个区间,50 ~ 99为第二个区间,依次类推。为此定义一个统计数组,假定用c[11]表示,用它的第一个元素c[0]统计用电量为0 ~ 49区间内的用户数,用它的第二个元素c[1]统计用电量为50 ~ 99区间内的用户数,......,用它的第11个元素c[10]统计用电量为大于等于500度的用户数。

在程序的主函数中,应首先定义数组c[11]并初始化每个元素的值为0;接着通过N次循环,从键盘上依次输入每户的用电量x,并统计到相应的元素中,即下标为x/50的元素中,当然若x>=500,则统计到c[10]元素中;最后通过循环输出在数组c中保存的统计结果。

根据分析编写出程序如下:

#include<iostream.h>

const int N=100; //假定N的值为100

void main()

{

int c[11]={0};

int i,x;

for(i=1;i<=N;i++) {

cin>>x;

if(x<500) c[x/50]++; else c[10]++;

}

for(i=0;i<=10;i++) cout<<"c["<<i<<"]="<<c[i]<<endl;

}

3.3 排序

例1. 已知有10个常数为42,65,80,74,36,44,28,65,94,72,编一程序,采用选择排序方法,按照从小到大的顺序打印输出。

分析:首先需要把已知的10个常数存入到一维数组中,假定该数组被定义为a[10];接着采用选择排序的方法对数组a[10]中的10个元素按照其值从小到大的顺序排序,使得元素值的排列次序与下标次序相同,即得到a[0]≤a[1]≤a[2]≤...≤a[9];最后按照下标次序显示出每个元素的值,它们是按值的从小到大的次序排列的。

对数组a中的n个元素进行选择排序共需要进行n-1次选择和交换的过程,第一次从待排序区间a[0] ~ a[n-1]中通过顺序比较选择出一个最小值元素,把它与该区间的第一个元素a[0]交换后,a[0]就成为所有n个元素中的最小值;第二次从新的待排序区间a[1] ~ a[n-1]中通过顺序比较选择出一个最小值元素,把它与当前区间的第一个元素a[1]交换后,a[1]就成为仅次于a[0]的最小值元素;依次类型,第n-1次(即最后一次)从当前待排序区间a[n-2] ~ a[n-1]中通过顺序比较选择出一个最小值元素,把它与当前区间的第一个元素a[n-2]交换后,整个排序过程结束,此时数组a中的所有n个元素就按照其值从小到大的次序排列了。

若一个数组中的元素是按照其值从小到大的次序排列的,则称之为有序表,否则称之为无序表。对于一个有序表若按照从小到大有序则又称为升序表或正序表,若按照从大到小有序则又称为降序表或逆序表。通常若不特别指明,所说的有序均为升序。

选择排序过程使用双重for循环来实现,设外循环变量为i,它需要从1顺序取值到n-1,其中n为待排序数组中元素的个数,每次的待排序区间为a[i-1] ~ a[n-1],这里假定a为数组名,设内循环变量为j,它需要从i顺序取值到n-1,每次取值都让a[j]同a[k]比较,若a[j]<a[k]成立则把j的值赋给k,使得a[k]始终为当前区间中已比较过的所有元素中的最小值,其中k的初值为i-1,每次从当前排序区间选择出最小值a[k]后,都要把它与a[i-1]的值相交换,使得a[i-1]成为当前区间中的最小值。

根据以上分析,编写出此题的完整程序如下:

#include<iostream.h>

int a[10]={42,65,80,74,36,44,28,65,94,72}; //在所有函数定义之外

//定义数组或变量可以为在它们之后定义的每个函数所使用

int n=10; //这里定义的数组a和n可为其后的两个函数所使用

void SelectSort() //选择排序算法

{

int i,j,k;

for(i=1;i<n;i++) { //进行n-1次选择和交换

k=i-1; //给k赋初值

for(j=i;j<n;j++) //选择出当前区间内的最小值a[k]

if(a[j]<a[k]) k=j;

int x=a[i-1]; a[i-1]=a[k]; a[k]=x; //交换a[i-1]与a[k]的值

}

}

void main()

{

SelectSort(); //调用函数对数组a[n]进行选择排序

for(int i=0;i<n;i++) cout<<a[i]<<' ';

//依次输出数组a[n]中的每个元素值

cout<<endl;

}

该程序的运行结果为:

28 36 42 44 65 65 72 74 80 94

例2. 已知10个常数如上例,请采用插入排序的方法对其进行排序并输出。

分析:插入排序方法的过程是:把数组a[n]中的n个元素看作为一个有序表和一个无序表,开始时有序表中只有一个元素a[0],无序表中包含有n-1个元素a[1] ~ a[n-1],以后每次从无序表中取出第一个元素a[i](i=1,2,...,n-1),就把它插入到前面有序表中的合适位置,使之仍为一个有序表,这样有序表就增加了一个元素,由上一次的a[0] ~ a[i-1]变为当前的a[0] ~ a[i],无序表中就减少了一个元素,由上一次的a[i] ~ a[n-1]变为当前的a[i+1] ~ a[n-1],经过n-1次插入过程后整个数组a中的n个元素就成为了一个有序表。

那么,如何在第i次把无序表中的第一个元素a[i]插入到前面有序表a[0] ~ a[i-1]中,使之成为一个新的有序表a[0] ~ a[i]。具体做法是:从有序表的表尾元素a[i-1]开始,依次向前使每一个元素a[j](j=i-1,i-2,...,0)同x(用x暂存待插入元素a[i]的值)进行比较,若x<a[j]则把a[j]后移一个位置,直到此条件不成立或j<0为止,此时已空出的下标为j+1的位置就是x的插入位置,接着把x的值存人a[j+1]即可。

根据分析编写出程序如下:

#include<iostream.h>

int a[10]={42,65,80,74,36,44,28,65,94,72};

int n=10;

void InsertSort() //插入排序算法

{

int i,j,x;

for(i=1;i<n;i++) { //进行n-1次循环,每次插入一个元素到有序表

x=a[i]; //将此次待插入元素存入x

for(j=i-1;j>=0;j--) //为x顺序向前寻找插入位置

if(x<a[j]) a[j+1]=a[j]; else break;

a[j+1]=x; //将x插入到已找到的插入位置

}

}

void main()

{

InsertSort(); //调用插入排序算法对数组a[n]进行排序

for(int i=0;i<n;i++) cout<<a[i]<<' '; //输出数组a[n]

cout<<endl;

}

3.4 查找

例1. 假定在一维数组a[10]中保存着10个整数42,55,73,28,48,66,30,65,94,72,编一程序从中顺序查找出具有给定值x 的元素,若查找成功则返回该元素的下标位置,否则表明查找失败返回-1。

此程序比较简单,假定把从一维数组中顺序查找的过程单独用一个函数模块来实现,把调用该函数进行顺序查找通过主函数来实现,则整个程序如下:

#include<iostream.h>

const int N=10; //假定把数组中保存的整数个数用常量N表示

int a[N]={42,55,73,28,48,66,30,65,94,72};

int SequentialSearch(int x) //顺序查找算法

{

for(int i=0;i<N;i++)

if(x==a[i]) return i; //查找成功返回元素a[i]的下标值

return -1; //查找失败返回-1

}

void main()

{

int x1=48,x2=60,f;

f=SequentialSearch(x1); //从数组a[N]中查找值为x1的元素,

//将返回值赋给f

if(f==-1) cout<<"查找"<<x1<<"失败!"<<endl;

else cout<<"查找"<<x1<<"成功!"<<"下标为"<<f<<endl;

//查找成功或失败分别显示出相应的信息

f=SequentialSearch(x2); //查找值为x2的元素,返回值赋给f

if(f==-1) cout<<"查找"<<x2<<"失败!"<<endl;

else cout<<"查找"<<x2<<"成功!"<<"下标为"<<f<<endl;

}

上机输入和运行该程序,得到的输出结果为:

查找48成功!下标为4

查找60失败!

例2. 假定一维数组a[N]中的N个元素是一个从小到大顺序排列的有序表,编一程序从a中二分查找出其值等于给定值x的元素。

分析:二分查找又称折半查找或对分查找。它比顺序查找要快得多,特别是当数据量很大时效果更显著。二分查找只能在有序表上进行,对于一个无序表则只能采用顺序查找。在有序表a[N]上进行二分查找的过程为:首先待查找区间为所有N个元素a[0] ~ a[N-1],将其中点元素a[mid](mid=(N-1)/2)的值同给定值 x进行比较,若x==a[mid]则表明查找成功,返回该元素的下标mid的值,若x<a[mid],则表明待查元素只可能落在该中点元素的左边区间a[0] ~ a[mid-1]中,接着只要在这个左边区间内继续进行二分查找即可,若x>a[mid],则表明待查元素只可能落在该中点元素的右边区间a[mid+1] ~ a[N-1]中,接着只要在这个右边区间内继续进行二分查找即可。这样经过一次比较后就使得查找区间缩小一半,如此进行下去,直到查找到对应的元素,返回下标值,或者查找区间变为空(即区间下界low大于区间上界high),表明查找失败返回-1为止。

根据以上的分析,编写出此题完整程序如下:

#include<iostream.h>

const int N=10; //假定N等于10

int a[N]={15,26,37,45,48,52,60,66,73,90}; //定义数组a[N]并初始化

int BinarySearch(int x) //二分查找算法

{

int low=0, high=N-1; //定义并初始化区间下界和上界变量

int mid; //定义保存中点元素下标的变量

while(low<=high) { //当当前查找区间非空时进行一次二分查找过程

mid=(low+high)/2; //计算出中点元素的下标

if(x==a[mid]) return mid; //查找成功返回

else if(x<a[mid]) high=mid-1; //修改high得到左区间

else low=mid+1; //修改low得到右区间

}

return -1; //查找失败返回-1

}

void main()

{

int b[3]={37,48,70}; //假定待查元素值用数组b表示

int f; //用于保存调用二分查找函数的返回值

for(int i=0;i<3;i++) {

f=BinarySearch(b[i]);

if(f!=-1)

cout<<"二分查找"<<b[i]<<"成功!"<<"下标为"<<f<<endl;

else cout<<"二分查找"<<b[i]<<"失败!"<<endl;

}

}

四、字符串

4.1 字符串概念

1. 字符串的定义

在C++语言中,一个字符串就是用一对双引号括起来的一串字符,其双引号是该字符串的起、止标志符,它不属于字符串本身的字符。如:

(1) “ string ”

(2) “ Visual C++ ”

(3) “ a+b= ”

(4) “ 姓名 ,年龄 ”

(5) “ Input a integer to x: ”

都是 C++字符串。

一个字符串的长度等于双引号内所有字符的长度之和,其中每个ASCII码字符的长度为1,每个区位码字符(如汉字)的长度为2。如上面每个字符串的长度依次为6,10,4,9和21。

特殊地,当一个字符串不含有任何字符时,则称为空串,其长度为0,当只含有一个字符时,其长度为1,如 ”” 是一个空串, ” A ” 是一个长度为 1的字符串。注意: ' A ' 和 ” A ” 是不同的,前者表示一个字符 A,后者表示一个字符串A,虽然它们的值都是A,但稍后便知它们具有不同的存储格式。

在一个字符串中不仅可以使用一般字符,而且可以使用转义字符。如字符串 ” \ ” cout<<ch\ ” \n ” 中包含有 11个字符,其中第1个和第10个为表示双引号的转义字符,最后一个为表示换行的转义字符。

2. 字符串的存储

在C++语言中,存储字符串是利用一维字符数组来实现的,该字符数组的长度要大于等于待存字符串的长度加1。设一个字符串的长度为n,则用于存储该字符串的数组的长度应至少为n+1。

把一个字符串存入到数组时,是把每个字符依次存入到数组中对应元素中,即把第一个字符存入到下标为0的元素中,第二个字符存入到下标为1的元素中,依次类推,最后把一个空字符 ' \0 ' 存入到下标为 n的元素中,这里假定字符串的长度为n。当然存储每个字符就是存储它的ASCII码或区位码。如利用一维字符数组a[12]来存储字符串 ” Strings.\n ” 时,数组 a中的内容为:

S

t

r

i

n

G

s

.

\n

\0

0 1 2 3 4 5 6 7 8 9 10 11

字符表示:

0 1 2 3 4 5 6 7 8 9 10 11

83

116

114

105

110

103

115

46

10

0

ASCII码表示:

若一个数组被存储了一个字符串后,其尾部还有剩余的元素,实际上也被自动存储上空字符 ' \0 ' 。在上述例子中, a[10]和a[11]元素的值也被自动置为 ' \0 ' 。

3. 利用字符串初始化字符数组

一个字符串能够在定义字符数组时作为初始化数据被存入到数组中,不能通过赋值表达式直接赋值。如:

(1) char a[10]= ” array ” ;

(2) char b[20]= ” This is a pen. ” ;

(3) char c[8]= ”” ;

(4) a= ” struct ” ;

(5) a[0]= ' A ' ;

第一条语句定义了字符数组a[10]并被初始化为 ” array ” ,其中 a[0] ~ a[5]元素的值依次为字符 ' a ' , ' r ' , ' r ' , ' a ' , ' y ' 和 ' \0 ' ;第二条语句定义了字符数组 b[20],其中b[i]元素(0≤i≤13)被初始化为所给字符串中的第i+1个字符,b[14]被初始化为字符串结束标志符 ' \0 ' ;第三条语句定义了一个字符数组 c[8]并初始化为一个空串,此时它的每个元素的值均为 ' \0 ' ;第四条语句是非法的,因为它试图使用赋值号把一个字符串直接赋值给一个数组,这在 C++中是不允许的;第五条是合法的,它把字符 ' A ' 赋给了 a[0]元素,使得数组a中保存的字符串变为 ” Array ” 。

利用字符串初始化字符数组也可以写成初值表的方式。如上述第一条语句与下面语句完全等效。

char a[10]={ ' a ' , ' r ' , ' r ' , ' a ' , ' y ' , ' \0 ' }; // ' \0 ' 也可直接写为 0

注意:最后一个字符 ' \0 ' 是必不可少的,它是一个字符串在数组中结束的标志。

4. 字符串的输入和输出

用于存储字符串的字符数组,其元素可以通过下标运算符访问,这与一般字符数组和其他任何类型的数组是相同的。除此之外,还可以对它进行整体输入输出操作和有关的函数操作。如假定a[11]为一个字符数组,则:

(1) cin>>a;

(2) cout<<a;

是允许的,即允许在提取或插入操作符后面使用一个字符数组名实现向数组输入字符串或输出数组中保存的字符串的目的。

计算机执行上述第一条语句时,要求用户从键盘上输入一个不含空格的字符串,用空格或回车键作为字符串输入的结束符,系统就把该字符串存入到字符数组a中,当然在存入的整个字符串的后面将自动存入一个结束符 ' \0 ' 。

注意:输入的字符串的长度要小于数组a的长度,这样才能够把输入的字符串有效地存储起来,否则是程序设计的一个逻辑错误,可能导致程序运行出错。另外,输入的字符串不需要另加双引号定界符,只要输入字符串本身即可,假如输入了双引号则被视为一般字符。

执行上述第二条语句时向屏幕输出在数组a中保存的字符串,它将从数组a中下标为0的元素开始,依次输出每个元素的值,直到碰到字符串结束符 ' \0 ' 为止。若数组 a中的内容为:

0 1 2 3 4 5 6 7 8 9 10

w

R

I

t

e

\0

r

e

a

d

\0

则输出a时只会输出第一个空字符前面的字符串 ” write ” ,而它后面的任何内容都不会被输出。

利用插入操作符<<不仅能够输出字符数组中保存的字符串,而且能够直接输出一个字符串常量,即用双引号括起来的字符串。如:

cout<< ” x+y= ” <<x+y<<endl;

此语句输出字符串 ” x+y= ” 后接着输出 x+y的值和一个换行符。若x和y的值分别为15和24,则得到的输出结果为:

x+y=39

5. 利用二维数组存储字符串

利用一维字符数组能够保存一个字符串,而利用二维字符数组能够同时保存若干个字符串,最多能保存的字符串个数等于该数组的行数。如:

(1) char a[7][4]={ ” SUN ” , ” MON ” , ” TUE ” , ” WED ” , ” THU ” , ” FRI ” , ” SAT ” };

(2) char b[][8]={ ” well ” , ” good ” , ” middle ” , ” pass ” , ” bad ” };

(3) char c[6][10]={ ” int ” , ” double ” , ” char ” };

(4) char d[10][20]={ ”” };

在第一条语句中定义了一个二维字符数组a,它包含7行,每行具有4个字符空间,每行用来保存长度小于等于3的一个字符串。该语句同时对a进行了初始化,使得 ” SUN ” 被保存到行下标为 0的行里,该行包含a[0][0],a[0][1],a[0][2]和a[0][3]这四个二维元素,每个元素的值依次为 ' S ' , ' U ' , ' N ' 和 ' \0 ' ,同样 ” MON ” 被保存到行下标为 1的行里,......, ” SAT ” 被保存到行下标为6的行里。以后既可以利用双下标变量a[i][j](0≤i≤6,0≤j≤2)访问每个字符元素,也可以利用只带行下标的单下标变量a[i](0≤i≤6)访问每个字符串。如a[2]则表示字符串 ” TUE ” , a[5]则表示字符串 ” FRI ” , cin>>a[4]则表示从键盘上向a[4]输入一个字符串,cout<<a[i]则表示向屏幕输出a[i]中保存的字符串。

上述第二条语句定义了一个二维字符数组b,它的行数没有显式地给出,隐含为初值表中所列字符串的个数,因所列字符串为5个,所以该数组b的行数为5,又因列下标的上界定义为8,所以每一行所存字符串的长度要小于等于7。该语句被执行后,b[0]表示字符串 ” well ” , b[1]表示字符串 ” good ” , ......。

第三条语句定义了一个二维字符数组 c,它最多能够存储6个字符串,每个字符串的长度要不超过9,该数组前三个字符串元素c[0],c[1]和c[2]分别被初始化为 ” int ” , ” double ” 和 ” char ” ,后三个字符串元素均被初始化为空串。

第四条语句定义了一个能够存储10个字符串的二维字符数组d,每个字符串的长度不得超过19。该语句对所有字符串元素初始化为一个空串。

下面的程序段能够从键盘上依次输入10个字符串到二维字符数组w中保存起来,输入的每个字符串的长度不得超过29。

const int N=10;

char w[N][30];

for(int i=0;i<N;i++) cin>>w[i];

下面的一条for语句将按相反的次序依次输出在数组w中保存的所有字符串,在输出每个字符串之后都输出一个换行符。

for(i=N-1;i>=0;i--) cout<<w[i]<<endl;

4.2 字符串函数

C++系统专门为处理字符串提供了一些预定义函数供编程者使用,这些函数的原型被保存在string.h头文件中,当用户在程序文件开始使用#include命令把该头文件引入之后,就可以在后面定义的每个函数中调用这些预定义的字符串函数,对字符串作相应的处理。

C++系统提供的处理字符串的预定义函数有许多,从C++库函数资料中可以得到全部说明,下面简要介绍其中几个主要的字符串函数。

1. 求字符串长度

函数原型: int strlen(const char s[]);

此函数只有一个参数,它是一个元素类型为字符的数组参数,它前面使用的保留字const表示该参数的内容在函数体中是不允许改变的,当然使用它不影响读取参数的值。该函数对应的实参可以为任何形式的字符串,如可以是一个字符串常量,可以是一个一维字符数组名,也可以是二维字符数组中只带行下标的单下标变量。待学习完下一章指针之后,读者将会对指针参数有更深刻的理解。

调用该函数时,将返回实参字符串的长度。

例如,假定一个字符数组a[10]的内容为 ”” , b[10]的内容为 ” a ” , c[20]的内容为 ” StringLength ” ,则 strlen(a),strlen(b)和strlen(c)的值分别为0,1和12。

若要计算字符串常量 ” constant ” 的长度,则使用 strlen( ” constsnt ” )即可得到,返回值为8。

2. 字符串拷贝

函数原型: char* strcpy(char* dest, const char* src);

此函数有两个参数,它们都是字符指针参数。因为每个字符指针是指向相应字符串的首地址,而字符数组名就是所存字符串的首地址,所以字符数组名也就是一个字符指针。字符指针参数说明同字符数组参数说明是等价的,也就是说,该函数中的两个参数说明分别同char dest[]和const char src[]是等价的。无论采用哪一种说明,dest或src都能够接受调用时由实参传送来的一个字符指针,即一个字符串存储空间的首地址。

该函数的功能是把第二个参数src所指字符串拷贝(即赋值)到第一个参数dest所指的存储空间(即dest字符数组)中,然后返回dest的值,它是一个字符指针。

因为该函数只需要从src字符串中读取内容,不需要修改它,所以用const修饰,而对于第一个参数dest,需要修改它的内容,所以就不能用const修饰。

关于指针的更详细的内容将在下一章讨论。

请看下面的程序段:

char a[10], b[10]= ” copy ” ;

strcpy(a,b);

cout<<a<< ' ' <<b<< ' ' ;

cout<<strlen(a)<< ' ' <<strlen(b)<<endl;

该程序段首先定义了两个字符数组a和b,并对b初始化为 ” copy ” ;接着调用 strcpy函数,把b所指向(即数组b保存)的字符串 ” copy ” 拷贝到 a所指向(即数组a占用)的存储空间中,使得数组a保存的字符串同样为 ” copy ” ,该函数返回 a的值被自动丢失;该程序段中的第三条语句输出a和b所指向的字符串,或者说输出数组a和b中所保存的字符串;第四条语句输出a和b所指向的字符串的长度。该程序段的运行结果为:

copy copy 4 4

3. 字符串连接

函数原型:char* strcat(char* dest, const char* src);

此函数同上述strcpy函数具有完全相同的参数说明和返回值类型。函数功能是把第二个参数src所指字符串拷贝到第一个参数dest所指字符串之后的存储空间中,或者说,把src所指字符串连接到dest所指的字符串之后。该函数返回dest的值。

使用该函数时要确保dest所指字符串之后有足够的存储空间用于存储src串。

调用此函数之后,第一个实参所指字符串的长度将等于两个实参所指字符串的长度之和。

例如:

char a[20]= ” string ” ; //字符串长度为6

char b[]= ” catenation ” ; //字符串长度为10

strcat(a, ” ” ); //连接一个空格到a串之后

strcat(a,b); //把b串连接到a串之后

cout<<a<< ' ' <<strlen(a)<<endl;

执行该程序段得到的输出结果为:

string catenation 17

4. 字符串比较

函数原型:int strcmp(const char* s1, const char* s2);

此函数带有两个字符指针参数,各自指向相应的字符串,函数的返回值为整型。

该函数的功能为:比较s1所指字符串与s2所指字符串的大小,若s1串大于s2串则返回一个大于0的值,在C++6.0中返回1;若s1串等于s2串则返回值为0;若s1串小于s2串则返回一个小于0的值,在C++6.0中返回-1。

比较s1串和s2串的大小是一个循环过程,需要从两个串的第一个字符起依次向后比较,整个比较过程可用下面的程序段描述出来。

int i;

for(i=0; s1[i] && s2[i]; i++)

//循环的正常结束要等到任一个字符串中的字符比较完

if(s1[i]>s2[i]) return 1;

else if(s1[i]<s2[i]) return -1;

if(s1[i]==0 && s2[i]==0) return 0;

//等于号右边的数值0可改为'\0'

else if(s1[i]!=0) return 1;

else return -1;

在这个程序段中使用的s1[i]和s2[i]分别为s1数组和s2数组中下标为i的元素,分别表示s1和s2所指字符串中的第i+1个字符。

假定字符数组a,b和c的值分别为字符串 ” 1234 ” , ” 4321 ” , ” 1304 ” ,则:

strcmp(a, ” 1234 ” )=0 strcmp(a,b)=-1

strcmp(a,c)=-1 strcmp(a, ” 123 ” )=1

strcmp( ” A ” , ” a ” )=-1 strcmp( ” 英文 ” , ” 汉字 ” )=1

5. 从字符串中查找字符

函数原型:char* strchr(const char* s, int c);

该函数从s所指字符串中的第一个字符起顺序查找ASCII码为c值的字符,若查找成功则返回该字符的存储地址,否则返回NULL(即数值0)。一个字符数组a中下标为i的元素a[i]的存储地址就是所存字符的存储地址,此地址可根据a+sizeof(char)*i计算出来,因为sizeof(char)=1,所以a[i]的地址为a+i的值。

当调用该函数时传送给第二个形参c的实参,可以为整数,但通常是一个待查找的字符。

例如,进行strchr( ” abcd ” , ' c ' )函数调用将返回字符串 ” abcd ” 的首地址加 2的值,进行strchr( ” abcd ” , ' e ' )函数调用将返回地址值NULL。

6. 从字符串中逆序查找字符

函数原型:char* strrchr(const char* s, int c);

它与上面介绍的strchr函数功能相同,都是从字符串中查找字符,但查找次序不同,该函数是从s所指字符串的最后一个字符起顺序向前查找,同样若查找成功返回字符的存储地址,否则返回NULL。

例如,进行strrchr( ” abcab ” , ' a ' )函数调用将返回字符串 ” abcab ” 的首地址加 3的值,若把函数名改为strchr,则结果为 ” abcab ” 的首地址值。

7. 从字符串中查找子串

函数原型:char* strstr(const char* s1, const char* s2);

该函数从第一个参数s1所指字符串中第一个字符起,顺序向后查找出与第二个参数s2所指字符串相同的子串,若查找成功则返回该子串的首地址,否则返回NULL。

例如:

char a[20]="abcdabxcdabxy";

char b[4]="abx";

char c[4]="axy";

cout<<strstr(a,b)<<endl;

if(strstr(a,c)==NULL)

cout<<"Not found!"<<endl;

cout<<strstr("学习文化知识","文化")<<endl;

该程序段首先定义了三个字符数组并分别进行了初始化,接着输出以strstr(a,b)的返回地址为首地址的字符串,该字符串为 ” abxcdabxy ” ,执行第四条 if语句时,因从a串中查找不到c串,所以判断表达式成立,将向屏幕输出字符串 ” Not found! ” 和一个换行符,执行最后一条语句时,输出的字符串为 ” 文化知识 ”