再上一篇:7.4 枚举类型
上一篇:第八章 指针和引用
主页
下一篇:8.3 指针数组与指向指针的指针变量
再下一篇:8.4 指针与函数
文章列表

8.2 指针与数组

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

在C++语言中,指针与数组有着密切的联系。可以用指针来访问数组中的任一元素。数组的起始地址称为数组的指针,而把指向数组元素的指针变量称为指向数组的指针变量。使用指向数组的指针变量来处理数组中的元素,不仅可使程序紧凑,而且还可以提高运算速度。

8.2.1 用指针访问数组元素

在C++语言中,当说明了一个数组后,数组名可以作为一个指针来使用,它的值为整个数组的起始地址,即数组的第0个元素的起始地址。但数组名不同于指针变量,编译程序要为指针变量分配内存空间,而编译程序并不为数组名分配内存空间,只是为每一个数组的元素依次分配一个连续的内存空间。因此,在程序中,可把数组名作为指针来使用,但不能对数组名进行赋值运算、++或--运算。8.2.1.1 一维数组与指针

我们分别讨论用指针来访问一维数组元素和多维数组元素,首先介绍一维数组与指针的关系。

当说明了一个与一维数组的类型相同的指针变量,并使这指针变量指向数组的第0个元素后,就可以使用该指针变量来取数组的元素值或修改数组中的元素值。我们先说明一组数组与指针之间的关系,然后再说明二维数组与指针之间的关系。设有语句:

int a[10],b[20];

int *pointa,*pointb;

pointa = &a[0];

pointb = b;

使指针变量pointa的值为数组a的起始地址,由于一维数组名表示数据中第0个元素的地址,也是整个数组的起始地址,因此使指针变量pointb的值为数组b的起始地址。使一个指针变量指向一个数组后,就可以使用这个指针变量来访问数组中的各个元素。

例8.5 用指针访问数组元素。

#include

void main(void )

{

int a[10] , i , j , *point ;

point = &a[0] ;

for ( i = 0 ; i < 10 ; i ++) *point++ = i; //A

point = a; //B

//输出数组的所有元素

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

cout << "a[" << i << "]=" << *point++<<'\t'; //C

cout<<'\n';

point = a; //D

//输出数组的所有元素

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

cout << "a[" << j << "]=" << *(point+j) << "\t"; //E

cout<<'\n';

//输出数组的所有元素

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

cout << "a["<< j << "]=" << *(a+j) << "\t"; //F

cout<<'\n';

//输出数组的所有元素

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

cout << "a[" << j << "]="<

cout<<'\n';

}

执行程序后的输出为:

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

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

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

a[0]=0 [1]=1 [2]=2 [3]=3 [4]=4 [5]=5 [6]=6 [7]=7 [8]=8

a[9]=9

程序中的A行是给数组中的各个元素进行赋值,其中表达式*point++ = i,首先将i的值赋给point所指向的内存单元,即将i的值赋给point所指向的数组元素;然后使point的值加1,使它指向数组中的下一个元素。当A行的循环语句执行完后,point已指向数组a的最后一个元素的后面。B行的作用是使得point指向数组的开始位置。C行中表达式*point++,它首先取出point所指向的数组元素的值输出,并使point指向数组中的下一个元素。D行的作用与B行相同,这一行不可省略。E行中的表达式*(point+j),因point+j表示数组a的第j个元素的地址,所以这表达式的值为a[j]的值。F行中的表达式*(a+j),由于a表示数组的第0个元素的地址,a+j为第j个元素的指针,这表达式的值也为a[j]的值。G行中表达式point[j],由于point的值为数组的起始地址,而a也为数组的起始地址,所以,可用point代替a,即point[j]与a[j]的作用相同。

尽管C行、E行、F行和G行的作用相同,均可实现输出数组的各个元素值,但C行的效率要比另外的三种实现方法高,这是由计算机的内部结构所确定的,其理由不作讨论了。

从本例中可以归结出以下几点:

1、数组名等同于数组的第0个元素的地址,也是整个数组的起始地址。

2、使point 指向数组a的第0个元素后,则point+i 等同于a+i ,其值为a[i]的地址,或者说是数组第i个元素的指针。

3、使point 指向数组a的第0个元素后,则*(point+i)、*(a+i )、a[i]、point[i]和*&a[i]彼此等同,都表示元素a[i]。

另外,必须强调的是,用指针来访问数组元素时编译程序不作下标是否越界的检查。

8.2.1.2 多维数组与指针

我们也可以用指针来访问多维数组的元素,某些方面的用法与一维数组类同,但有些方面的用法不同于一维数组。我们只讨论二维数组与指针的用法。当掌握二维数组与指针的用法后,可直接推广到三维或更多维数组。

在C++语言中,二维数组的各个元素值是按行的顺序逐行来存放的,编译程序也是为二维数组分配一个连续的内存空间来依次存放各个元素值的。当然,可将二维数组分配的连续内存空间作为一维数组来使用。当使得一个与二维数组同类型的指针变量指向二维数组的起始地址后,可以使用这个指针变量来访问二维数组中的各个元素。对于二维数组,要区分整个数组的指针(起始地址),每一行的指针和某一个元素的指针(某行某列的指针)。设有说明语句:

int a[4][4] = {{1,2,3,4},{5,6,7,8},{9,10, 11,12},{13,14,15,16}};在C++中,允许这样来理解二维数组:a 是一个二维数组名,类同于一维数组,它可以表示这个二维数组的起始地址;并可将二维数组的每一行看成一个元素,即数组a包含了四个元素a[0] , a[1], a[2] ,a[3];这四个元素依次又是一个一维数组, a[0] , a[1], a[2] ,a[3]又分别表示这四个一维数的起始地址(指针),并等同四个一维数组的数组名, 而每一个一维数组又包含了四个元素,如图8.3所示。

┏━━━━┳━━━━┳━━━━┳━━━━┓

a[0]:┃a[0][0] ┃a[0][1] ┃a[0][2] ┃a[0][3] ┃

┣━━━━╋━━━━╋━━━━╋━━━━┫

a[1]:┃a[1][0] ┃a[1][1] ┃a[1][2] ┃a[1][3] ┃

┣━━━━╋━━━━╋━━━━╋━━━━┫

a[2]:┃a[2][0] ┃a[2][1] ┃a[2][2] ┃a[2][3] ┃

┣━━━━╋━━━━╋━━━━╋━━━━┫

a[3]:┃a[3][0] ┃a[3][1] ┃a[3][2] ┃a[3][3] ┃

┗━━━━┻━━━━┻━━━━┻━━━━┛

图8.3 二维数组的每一行作为一个一维数组

数组名a代表了二维数组的起始地址,也就是第0行的首地址。当然对于二维数组来说,整个数组的起始地址、数组的第0行的首地址和数组的第0行第0列元素的地址值是相同的。a+i表示了第i行的第0列元素的起始地址,而a[i]也表示了第i行的第0列元素的起始地址。由于a[i]表示一个一维数组的首地址,并可作为一维数组来看待,所以a[i]+j表示了第i行第j列元素的指针。*(a[i]+j)表示元素a[i][j]。

例8.6 输出二维数组的行列地址和元素值。

#include

#include

void main(void )

{

int a[4][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};

int i, j ;

//输出的值都相同, 都是第i行的起始地址或第i行第0列元素的地址

cout<<"用四种不同的方式输出数组a每一行的起始地址:\n";

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

cout<

int *p ;

//输出数组的所有元素

cout<<"用指针输出数组的全部元素:\n";

for ( p = (int * )a, i = 0 ; i < 16 ; i ++){ //B

if ( i && i % 4 == 0 ) cout << "\n";

cout << *p++ << "\t";

}

cout<<'\n';

//输出的值都相同, 都是第i行第j列的数组元素

cout<<"用三种不同的方法输出数组的元素:\n";

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

for ( j= 0 ; j < 4 ; j++) {

cout <<*(a[i]+j)<<"\t"<<*(*(a+i)+j)<<"\t"<

}

//输出数组的所有元素

cout<<"用指针输出数组的各个元素:\n";

for ( i = 0, p = a[0]; p <= a[3]+3 ; p++,i++){ //D

if ( i && i % 4 == 0 ) cout << "\n";

cout <

}

cout<<'\n';

//输出数组的所有元素

cout<<"用另一种判数组元素结束条件的方法输出数组的元素:\n";

for ( i = 0, p = &a[0][0]; p <= &a[3][3] ; p++,i++){ //E

if (i && i % 4 == 0 ) cout << "\n";

cout <

}

}

执行程序后的输出为:

用四种不同的方式输出数组a每一行的起始地址:

0x0064FDB4 0x0064FDB4 0x0064FDB4 0x0064FDB4

0x0064FDC4 0x0064FDC4 0x0064FDC4 0x0064FDC4

0x0064FDD4 0x0064FDD4 0x0064FDD4 0x0064FDD4

0x0064FDE4 0x0064FDE4 0x0064FDE4 0x0064FDE4

用指针输出数组的全部元素:

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15 16

用三种不同的方法输出数组的元素:

1 1 1

2 2 2

3 3 3

4 4 4

5 5 5

6 6 6

7 7 7

8 8 8

9 9 9

10 10 10

11 11 11

12 12 12

13 13 13

14 14 14

15 15 15

16 16 16

用指针输出数组的各个元素:

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15 16

用另一种判数组元素结束条件的方法输出数组的元素:

1 2 3 4

5 6 7 8

9 10 11 12

13 14 15 16

程序中的 A 行用四种不同的表示方法输出了每一行的起始地址,a[i]、&a[i]、*(a+i)、&a[i][0]和a+i的值相同。为什么a[i]和&a[i]的相同呢?为什么a[i]已表示数组a的第i行第0列元素的地址值,而加了一个取地址运算符后,&a[i]仍表示相同的值呢?这是二维数组与一维数组之间的差异之一。尽管a[i]和&a[i]都表示地址,且值相同,但二者表示的意义是不同的。前者表示数组第i行第0列元素的地址值,而后者表示第i行的地址值。因编译器并不为数组名a和a[i]分配内存空间,只有为某一变量分配了内存空间后,变量名前的&才表示取地址运算符,所以在 a[i]前是否加&,是用来区分元素地址和行地址的。客观地说,&a[i]和a+i表示的物理意义相同,均表示第i行的起始地址。为什么*(a+i)与a+i的值相同呢?这里的“*”并不表示由a+i指针值所指向的内容,也是用于区分元素地址和行地址的。尽管*(a+i)与a+i的值相同,但前者是表示数组a的第i行第0列元素的地址,而后者表示数组a的第i行的地址。如表达式:

a+i+j

表示数组a第i+j行的起始地址,而表达式:

*(a+i)+j

表示数组a的第i行第j列的地址,如果不加上这个“*”,就无法区分到底是数组a的第i+j行的起始地址,还是数组第i行第j列的地址。表达式:

&a[i]+j

表示数组a第i+j行的起始地址,而不是第i行第j列的地址。这一点应特别注意。如,设有说明:

int *p1,*p2,b[5][4],i;

则以下语句是合法的:

p1=a[i];

表示将a数组的第i行第0列元素的地址赋给p1。但以下语句是错误的:

p2= &a[i];

因p2是指向一个整数的指针,而&a[i]是二维数组的行指针,所以二者的类型是不同的,不能直接赋值。当然,作强制类型转换后,进行赋值是允许的。如:

p2=int (*) &a[i];

是合法的。

程序中的B行,是把二维数组作为一维数组来处理的,当p指向数组a的起始地址后,p的值每加1后,就指向其后面的一个元素。设有说明语句:

int x[20][30],*px=&x[0][0],i,j;

则px+i*30+j与&x[i][j]的值相同,因每一行中有30个元素,所以px+i*30表示数组x的第i行第0列元素的起始地址,px+i*30+j表示数组x的第i行第j列的起始地址。显然, *(px+i*30+j)和x[i][j]的值相同。

在例中的C行,因a[i]+j和*(a+i)+j都表示数组a第i行第j列元素的指针,所以,*(a[i]+j)和*(*(a+i)+j)都表示元素a[i][j]。D行和E行也是把二维数组作为一维数组来使用的。

结合本例的说明,可把二维数组的行地址和元素地址的各种表示方法及含义归结为表8.1。

表8.1 数组地址和元素的表示法

表示形式 含义

a 二维数组名,表示数组的起始地址,数

组第0行的地址

a+0 第0行的起始地址

a[0] 第0行第0列元素的起始地址

*a,*(a+0) 第0行第0列元素的地址

**a,**(a+0),*a[0],*(*(a+0)+0) 表示元素a[0][0]

a+i,&a[i] 第I行的起始地址

a+i+j,&a[i]+j 第I+j行的起始地址

a[i],*(a+i) 第I行第0列元素的起始地址*(a+i)+j,a[i]+j,&a[i][j],* &a[i]+j 第I行第j列元素的地址

*(*(a+i)+j),*(a[i]+j),*(&a[i][j]) 第I行第j列元素的值

,*(* &a[i]+j),a[i][j]

8.2.2 指针与字符串

由于字符串是存放在字符数组中的,对字符数组中的字符逐个处理时,前面介绍的指针与数组之间的关系完全适用于字符数组。通常,把字符串作为一个整体来使用的,用指针来处理字符串更加紧凑和方便。当用指向字符的指针来处理字符串时,并不关心存放字符串的数组的大小,而只关心是否已处理到了字符串的结束字符。

例8.7 用指针实现字符串的拷贝。

#include

#include

void main(void)

{

char s1[] = "I am a student!"; //A

char *s2 = "You are a student!"; //B

char s3[30], s4[30], s5[30];

int i;

char *p1=s3, *p2=s1;

for ( ; *p1++ = *p2++; ); //C

for ( i = 0 ; i <= strlen(s1) ; i ++ ) //D

s4[ i] = s1[i];

strcpy ( s5, s2);

cout << "s3=" << s3 << "\n";

cout << "s4=" << s4 << "\n";

cout << "s5= " << s5 << "\n";

cout << "s2 = "<

}

执行程序后的输出为:

s3=I am a student!

s4=I am a student!

s5= You are a student!

s2 = You are a student!

程序中的A行说明了字符数组s1,并用一个字符串对其进行初始化,系统根据字符串的长度自动确定数组的大小。这等同于定义数组s1的大小为16。编译程序的处理过程是:先为数组s1分配16个字节的内存空间,然后将字符串"Iam a student!"依次存放到数组s1中。B行说明了一个字符串指针变量s2,并对该指针变量初始化。编译程序先将字符串"You are a student!"存放在某一个内存空间中,并将存放该字符串的首地址赋给指针变量s2。尽管A行和B行中“=”右边都是一个字符串,但编译程序的处理方法及含义都是不一样的,初学者必须注意这一点。

C行实现了字符串的拷贝,将指针变量p2所指向的字符串拷贝到p1所指向的字符数组中。for()语句的循环体为空语句,字符串的拷贝是由循环语句中的条件表达式:

*p1++ = *p2++

来实现的。开始时,p2指向s1[0],p1指向s3[0],第一次执行这个条件表达式时,将s1[0]中的字符赋给s3[0],并使p2指向s1[1],p1指向s3[1];由于s1[0]中字符不是字符串结束字符,继续执行这一条件表达式;直到到将p2指向的字符串结束符拷贝到p1所指向的地址中时,结束这一循环语句的执行。

从上例可以看到,用字符型指针变量与字符数组都可以用来处理字符串, 但二者在使用上是有区别的, 在使用的过程中要注意以下几点:

1、二者在概念上不一样。如说明语句:

char *pc,str[100];

编译程序要为字符数组str分配100个字节内存空间,它至多可以存放100个字符;而为指针变量pc仅分配4个字节的内存空间,它只能存放一个内存单元的地址。

2、尽管赋初值的形式类同,但含义不一样。如说明语句:

char str[] = " I am a student!",s[200];

char *str2 = "You are a student!" ,str3[100],*str4,*str5;对于字符数组,是把字符串送到为数组分配的存储空间去;而对于字符型指针,是先把字符串存放到内存中,然后将存放字符串的起始地址送到指针变量中。

3、赋值的方式不一样。对字符数组的赋值,必须是逐个元素的赋值, 但可将任一指针值赋给字符指针变量。如以下的赋值是错误的:

str3 = str;

s = "I love China!";

因编译器不给数组名分配内存空间,只是数组名表示数组的起始地址,所以不能将一个指针赋给数组名。以下的赋值是正确的:

str4 = str;

str5 = "I love China!";

经赋值后,使str4指向字符数组str。由于字符串表达式"I love China!"的值是一个指针,即存放该字符串的起始地址,将这指针值赋给指针变量当然是允许的。

4、可以给字符数组直接输入字符串,在给字符指针变量赋初值前,不允许将输入的字符串送到指针变量所指向的内存区域。如:

char s1[50], *chptr,s2[200];

cin >> s1; //是可以的

cin >> chptr; //是不可以的, 因不知道chptr指向什么样的内存单元

chptr= s2;

cin >> chptr; //可以,因chptr指向了一个已分配的内存空间

5、在程序的执行期间, 字符数组的起始地址是不能改变的, 而字符指针的值是可以改变的。如:

chptr = s1 + 5; //可以

chptr = chptr + 5; //可以

s1 = s1 + 2; //不可以