C语言之指针数组函数

C语言之指针数组函数
C语言之指针数组函数

C语言之指针、数组和函数

基本解释

1、指针的本质是一个与地址相关的复合类型,它的值是数据存放的位置(地址);数组的本质则是一系列的变量。

2、数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。

3、当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。问题:指针与数组

听说char a[]与char*a是一致的,是不是这样呢?

答案与分析:

指针和数组存在着一些本质的区别。当然,在某种情况下,比如数组作为函数的参数进行传递时,由于该数组自动退化为同类型的指针,所以在函数内部,作为函数参数传递进来的指针与数组确实具有一定的一致性,但这只是一种比较特殊的情况而已,在本质上,两者是有区别的。请看以下的例子:

char a[]="Hi,pig!";

char*p="Hi,pig!";

上述两个变量的内存布局分别如下:

数组a需要在内存中占用8个字节的空间,这段内存区通过名字a来标志。指针p则需要4个字节的空间来存放地址,这4个字节用名字p来标志。其中存放的地址几乎可以指向任何地方,也可以哪里都不指,即空指针。目前这个p 指向某地连续的8个字节,即字符串“Hi,pig!”。

另外,例如:对于a[2]和p[2],二者都返回字符‘i’,但是编译器产生的执行代码却不一样。对于a[2],执行代码是从a的位置开始,向后移动2两个字节,然后取出其中的字符。对于p[2],执行代码是从p的位置取出一个地址,在其上加2,然后取出对应内存中的字符。

问题:数组指针

为什么在有些时候我们需要定义指向数组而不是指向数组元素的指针?如何定义?

答案与分析:

使用指针,目的是用来保存某个元素的地址,从而来利用指针独有的优点,那么在元素需要是数组的情况下,就理所当然要用到指向数组的指针,比如在高维需要动态生成情况下的多维数组。

定义例子如下:int(*pElement)[2]。

下面是一个例子:

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

int(*pa)[3];//定义一个指向数组的指针

pa=&array[0];//'&'符号能够体现pa的含义,表示是指向数组的指针

printf("%d",(*pa)[0]);//将打印array[0][0],即1

pa++;//猜一猜,它指向谁?array[1]?对了!

printf("%d",(*pa)[0]);//将打印array[1][0],即4

上述这个例子充分说明了数组指针—一种指向整个数组的指针的定义和使用。

需要说明的是,按照我们在第四篇讨论过的,指针的步进是参照其所指对象的大小的,因此,pa++将整个向后移动一个数组的尺寸,而不是仅仅向后移动一个数组元素的尺寸。

问题:指针数组

有如下定义:

struct UT_TEST_STRUCT*pTo[2][MAX_NUM];

请分析这个定义的意义,并尝试说明这样的定义可能有哪些好处?

答案与分析:

前面我们谈了数组指针,现在又提到了指针数组,两者形式很相似,那么,如何区分两者的定义呢?分析如下:

数组指针是:指向数组的指针,比如int(*pA)[5]。

指针数组是:指针构成的数组,比如int*pA[5]。

至于上述指针数组的好处,大致有如下两个很普遍的原因:

a)、各个指针内容可以按需要动态生成,避免了空间浪费。

b)、各个指针呈数组形式排列,索引起来非常方便。

在实际编程中,选择使用指针数组大多都是想要获得如上两个好处。

问题:指向指针的指针

在做一个文本处理程序的时候,有这样一个问题:什么样的数据结构适合于按行存储文本?

答案与分析:

首先,我们来分析文本的特点,文本的主要特征是具有很强的动态性,一行文本的字符个数或多或少不确定,整个文本所拥有的文本行数也是不确定的。这样的特征决定了用固定的二维数组存放文本行必然限制多多,缺乏灵活性。这种场合,使用指向指针的指针有很大的优越性。

现实中我们尝试用动态二维数组(本质就是指向指针的指针)来解决此问题:图示是一个指针数组。所谓动态性指横向(对应每行文本的字符个数)和纵向(对应整个文本的行数)两个方向都可以变化。

就横向而言,因为指针的灵活性,它可以指向随意大小的字符数组,实现了横向动态性。

就竖向而言,可以动态生成及扩展需要的指针数组的大小。

下面的代码演示了这种动态数组的用途:

//用于从文件中读取以'/0'结尾的字符串的函数

extern char*getline(FILE*pFile);

FILE*pFile;

char**ppText=NULL;//二维动态数组指针

char*pCurrText=NULL;//指向当前输入字符串的指针

ULONG ulCurrLines=0;

ULONG ulAllocedLines=0;

while(p=getline(pFile))

{

if(ulCurrLines>=ulAllocedLines)

{

//*当前竖向空间已经不够了,通过realloc对其进行扩展。

ulAllocedLines+=50;//每次扩展50行。

ppText=realloc(ppText,ulAllocedLines*(char*));

if(NULL==ppText)

{

return;//内存分配失败,返回

}

}

ppText[ulCurrLines++]=p;//横向“扩展”,指向不定长字符串

问题:指针数组与数组指针与指向指针的指针

指针和数组分别有如下的特征:

指针:动态分配,初始空间小

数组:索引方便,初始空间大

下面使用高维数组来说明指针数组、数组指针、指向指针的指针各自的适合场合。

多维静态数组:各维均确定,适用于整体空间需求不大的场合,此结构可方便索引,例a[10][40]。

数组指针:低维确定,高维需要动态生成的场合,例a[x][40]。

指针数组:高维确定,低维需要动态生成的场合,例a[10][y]。

指向指针的指针:高、低维均需要动态生成的场合,例a[x][y]。

问题:数组名相关问题

假设有一个整数数组a,a和&a的区别是什么?

答案与分析:

a==&a==&a[0],数组名a不占用存储空间。需要引用数组(非字符串)首地址的地方,我一般使用&a[0],使用a容易和指针混淆,使用&a容易和非指针变量混淆。

区别在于二者的类型。对数组a的直接引用将产生一个指向数组第一个元素的指针,而&a的结果则产生一个指向全部数组的指针。例如:

int a[2]={1,2};

int*p=0;

p=a;/*p指向a[0]所在的地方*/

x=*p;/*x=a[0]=1*/

p=&a;/*编译器会提示你错误,*/

/*显示整数指针与整数数组指针不一样*/

问题:函数指针与指针函数

请问:如下定义是什么意思:

int*pF1();

int(*pF2)();

答案与分析:

首先清楚它们的定义:

指针函数,返回一个指针的函数。

函数指针,指向一个函数的指针。

可知:

pF1是一个指针函数,它返回一个指向int型数据的指针。

pF2是一个函数指针,它指向一个参数为空的函数,这个函数返回一个整数。

指向数组的指针和指针数组在说明时有些相似,但是这完全是两个不同的概念,前者是指针,后者是数组。

1.指向象组的指针

指向数组的指针可以分为如下两类:

(1)指向数组元素的指针

指向数组元紊的指针是指向数组中的某个元素,可以是数组的首元素,也可以是数组的任一元素,只要将数组的某个元素的地址斌给它,则它便指向该元素。指向数组元素的指针是一级指针,它可以指向一维数组的某个元素,也可以指向二维数组或三维数组的某个元紊。例如:

int a[10],*P,

P=&a[9];

P是一个指向数组元素的指针,它指向一维数组a的第9个元素a[9]

[例7.20]一个指向二维数组元素的指针的例子。分析下列程序翰出结果。

int m[2][3]={(1,3,5),{7,9,1i},

main()

int

for(p-m[0];p

{

if((p一m[0])%3==0)

printf("\n"),

printf("%4d",*p).

}

printf("\n"),

}

执行该程序输出结果如下:

135

7911

说明:该程序中定义了一个指针P,它指向数组m的首元素,因为m[0]表示该数组第。行首列元素的地址,与&[0][0]等价。因此,P是一个指向二维数组m的首元素的指针。

请将P指针向数组m的尾元素,输出如下结果:

1197

531

2)指向一维数组的指针

指向数组的指针可以指向一维数组,也可以指向二维数组,这里只讨论指向一维数组的指针。

上面讨论过指向数组元素的指针,它是指向数组的某一个元素。而这里讨论的指向一维数组的指针是指它指向的不是一个元素,而是由若干个元素组成的一维数组。例如:

int(,pa)[5];

Pa是一个指向一维数组的指针,它所指向的一维数组是由5个int型元素组成的。如果该指针加1,则将指向下面的5个元素oPa指针的增值是以5个元素的一维数组长度为单位的。实际上,指向一维数组的指针,是指向二维数组的某一列的首元素地址。例如:

int(*pa)[5];

int a[3][5];

pa=a+1;

这表明pa是一个指向一维数组的指针,它指向二维数组a的第一行首列元素的地址。pa 加1将指向第二行首列元素,pa-1将指向第0行首列元素。

[例7.21]一个指向一维数组指针的例子。分析该程序的输出结果,说明指向一维数组的指针是怎祥赋值和使用的。

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

main(}

int(*p)[5];

p=a+1;

priatf("%d,%d,%d\n",p[0][0],*(*(p+1)+1),*(p[-1]+3

}

执行该程序输出结果如下:

6,12

说明;程序中p是一个指向一维数组的指针,它所指向的一维数组有5个int型元素。即p要指向一个包含有5个元素的一维数组。P的值是该一维数组的首元素地址。用a十1或&a[1]值赋给P,则使p指向a数组的第一行首列地值。不能用某行某列的地值给p赋值,P[0][0]等价**P,即a[[1][0]元素的值。*(*(p+1)+1)等价于p[1][1],即a[2}[1]元素的值。而*(P[-1]+3)等价于,*(*(p-1)+3),即a[0][3]元素的值。因此,输出上述结果。

实际上,指向一维数组的指针是一个二级指针,它所指向的是一个二维数组,给指向一维数组的指针所赋的值是二维数组的某行的行地址。二维数组a[3][5]的行地址表示为a十i,i 为0,1,2.一个指向一维数组的指针(*p)[5]可以用a+i(i=0,1,2)来进行赋值。

请读者思考下面问题:下述的程序段是否正确。

int a[3][5];

Ent(*P)[3];

p=a

指向数组的指针常常用来作函数的参数,这方面间题下一节讲述。

2.指针数组

指针数组是指数组元素为指针的数组。以前讲过数组元素可以为int型,float型和double 型数值,称为数值数组;数组元素为char型的称为字符数组;数组元素为指针的称为指针数组,最常用的是一维一级指针数组,即该数组是一维的,它的每个元素是一个一级指针,或是一维数组,因此一维一级指针数组实际上是一个二维数组。此外,还有一维二级指针数组和二维一级指针数组,它们实际上是一个三维数组。其他的指针数组使用甚少。

指针数组在定义形式上与指向数组的指针很相似,使用中应注意区别。指针数组定义格式如下:

{类型说明符)*<数组名)[(大小)]

这是一维一级指针数组的定义格式。而指向一维数组的指针定义格式如下:

<类型说明符>(*(数组名))[(大小)]

比较可知,二者只差一个圆括号,这个圆括号的作用是改变了优先级。没有圆括号的是数组名与[」结合,则是数组,前边的,表明数组元素的类型是某种类型的指针。有了圆括号的是,与数组名结合,则是指针,后面的仁1说明是指向一维数组的指针,例如:

int,*P[5],

int(*g)[5]

其中,P是一维一级指针数组名,g是指向一维数组的指针名。

一维二级指针数组和二维一级指针数组表示如下:

int**al[5],,*a2[3][5];

其中,al是一维二级指针数组名,a2是二维一级指针数组名。

指针数组的赋初值和赋值办法与数组一样,只是所赋予的值是地址值。

[例7.22]一个指针数组的例子。分析下列程序的输出结果,说明指针数组的赋值方法。

int,a[]={1,2,3,4,5.6}.

main()

{

int,*p[3]

for(i=0i<3,i++)

p[i]=&a[i*2];

prinif('%d,d\n"",p[1][1],,*(p[2]+1)):

}

执行该程序输出如下结果:

4,6

说明:该程序中p是一个一维一级指针数组名,它有3个元素,即每个元常是指向Int

型的指针。通过for循环给P赋值,分别使P[0]值为&a[0],p[1]值为&a[2],p〔2」值为&a[4].指针数组比较适合于对若干字符串的处理。字符串本身是一个一维字符数组,如将若干个字符串放到一个二维字符数组中,则每一行的元素个数要求相同,实际上各字符串长度不等,只好按字符串中最长的作为每行的元素个数。这样将会造成内存空间的浪费。如果采用指针数组便可克服存储空间的浪费问题。因为指针数组中各个指针元素可以指向不同长度的字符串。因此,实际编程中常用字符型的指针数组存放字符串。

[例7.23]分析下列程序的输出结果。

char,name's={"',,"Monday","`Fuesday","Wednesday","Thursday",

"friday,"Saturday","Sunday"),

main()

int week;

while(1)

{

priritf("Enter week No.:").

scanf("%d,&week");

if(week7)

break;

printf("week No.%d一一>%s\n",week,name[week]);

}

}

执行该程序输出如下结果:

Enter week No.:3

week N().3一一>Wednesday

Enter weep No.:7

Week NO.7一一>Sunday

Enter week NU.:o

退出该程序。

说明:该程序中name是一个字符型的指针数组名,用它来存放字符串。使用字符型指针数组存放不等长度的字符串不浪费存储空间。该程序的功能是将输入的数字表示的星期几转换成为英文单词表示的星期几。使用指针数组将数组下标的数字与英文单词对应起来,十分方便的完成该程序的功能。

[例7.25]使用图解法分析下列程序的输出结果。

程序内容如下:

char,str[3][2]=("abc'","def","ghijk","lmno",

"pyrstuvw","xyz};

char,**pc[]=(str[2],str[0]*str[1]};

char***ppc=pc;

main()

{

printf("%S\n",*ppc[2]);

printf("%s\n",**ppc+2);

prints("%s\n,*++十+PPc+2).

prizttf("%s\n",PPc[0][-1]+1);

printf("%s\n",*(*(ppc一1)+1));

}

执行该程序输出结果如下:

ghijk

rstuvw

f

bc

xyz

说明:

(1)图解法是一种对程序的辅助分析方法。有些程序中变量之间关系比较复杂,为了理顺关系可以使用图示的方法直观清楚地将各变量之间关系表示出来,在使用指针数组的程序中常用这种方法。下面以本题为例说明图解法的用法。

本题中有三个变量:str是一个二维一级指针数组,它存放了6个不同长度的字符串。pc是一个一维二级字符数组,它有3个元素,每个元素是一个二维字符数组或一维一级字符指针数组。ppc是一个三级字符指针。根据程序中给出关系,将它们用图解表示如下:

从上图中可以看出PPc是一个指向pc数组首元素的指针。Pc数组有3个元素,每个元素是一个指针,pc[0]指向str[2],pc[1]指向str[O],pc[2]指向str[l]。

具体分析:ppc是一个三级指针,而pc可认为是三维数组的数组名,或认为是一个三级指针名。pc有3个元家,每个元素应该是一个二级指针。str是一个二维一级指针数组名。str[i]也是一个数组名,它是一个一维一级指针数组名,它有2个元素,每个元素是一个字符串。

(2)进一步分析5个printf()语句的输出结果。

第一个printf()语句,按%s输出*ppc[2]的值,由于ppc指向pc,ppc[2]等价于*(ppc+2),即指向str[1],*PPc[2]等价于,*pPC[2]+O),即是字符串"ghijk"首元素地址。因此,输出字符串为ghijk.

第二个prints()语句,按%s输出**ppc+2的值。**PPc等价于*(*(ppc+O)+0),即是str[2]数组的首列元素的首地址值,即字符串''pqrstuqvw"的首元未地址值,**ppc+2则是该字符串中的r字符的地址值。因此,输出字符串为rstuvw.

第三个print()语句,按%s输出地址值*++*++ppc+1所指的字符串。*++ppc

等价于pc[1],它指向str[0],而*++*++ppc将是指向字符"def"的指针,*++*++

PPc+2是字符串"def"中字符f的地址。因此,输出字符串为f.这里需要注意的是ppc由于经过++运算后,已经指向pc〔l〕了,同理,str[O]也已经指向字符串"def"了。这便是由++运算符副作用所造成的。如图中虚线所示。

第四个printf()语句,按%s输出PPc[O][一1]+1地址值所指向的字符串。PPc[0][-1]等价于*(*(ppc+0)-1),这时PPc指向Pc[1],PPc[0][-1]指向字符串"abc"首地址(因为PPC[0][0]已是指向"def"的首地址,PPc[0][-1]+1指)向字符串"abc"中的b字符。因此,输出字符串为bc.

第五个printf()语句,将%s输出,(*(*(ppc-1)+1)所指向的字符串。*(*"(PPc 一1)+1)等价于PPc[-1][1],这时ppc仍指向pc[1],*(ppc-1)指向pc[0],*(*(ppc-1)+1指向字符串"xyz"的首地址。因此,输出字符串为xyz.

相关主题
相关文档
最新文档