文档库 最新最全的文档下载
当前位置:文档库 › c语言数组和指针的学习

c语言数组和指针的学习

c语言数组和指针的学习
c语言数组和指针的学习

C语言数组与指针详解2008-08-11 02:01在C语言中,指针和数组有着紧密的联系,其原因在于凡是由数组下标完成的操作皆可用指针来实现。在数组中我们已经知道,可以通过数组的下标唯一确定了某个数组元素在数组中的顺序和存储地址,这种访问方式也称为"下标方式"。例如:

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

x=a[2]; /* 通过下标将数组a下标为2的第3个元素的值赋给x,x=3 */

y=a[4]; /* 通过下标将数组a下标为4的第5个元素的值赋给y,y=5 */

由于每个数组元素相当于一个变量,因此指针变量既然可以指向一般的变量,同样也可以指向数组中的元素,也就是可以用"指针方式"访问数组中的元素。

例10-6:分析程序的运行过程和结果。

#include

main ( )

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

int x, y, *p;/* 指针变量p */

p = &a[0]; /* 指针p指向数组a的元素a[0],等价于p=a */

x = *(p+2); /* 取指针p+2所指的内容,等价于x=a[2] */

y = *(p+4); /* 取指针p+4所指的内容,等价于y=a[4] */

printf ("*p=%d, x=%d, y=%d\n", *p, x, y);

}

语句"p=&a[0]"表示将数组a中元素a[0]的地址赋给指针变量p,则p就是指向数组首元素a[0]的指针变量,"&a[0]"是取数组首元素的地址。

C语言中规定,数组第1个(下标为0)元素的地址就是数组的首地址,同时C中还规定,数组名代表的就是数组的首地址,所以,该语句等价于"p=a;"。注意,数组名代表的一个地址常量,是数组的首地址,它不同于指针变量。

对于指向数组首地址的指针p,p+i(或a+i)是数组元素a[i]的地址,*(p+i)(或*(a+i))就是a[i]的值,其关系如图10-5所示。

图10-5 指针操作与数组元素的关系

对数组元素的访问,下标方式和指针方式是等价的,但从C语言系统内部处理机制上讲,指针方式效率高。需要注意的是:指针方式不如下标方式直观。下标方式可以直截了当地看出要访问的是数组中的哪个元素;而对于指向数组的指针变量,进行运算以后,指针变量的值改变了,其当前指向的是哪一个数组元素不再是一目了然。

例10-7:分析程序。

main( )

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

int *p;

p = a; /* 指针p为数组的首地址*/

printf("%d", *p);

printf(" %d\n", *(++p)); /* 以下两个语句等价*/

printf("%d", *++p);

printf(" %d\n", *(p--)); /* *(p--)等价于*p-- */

p += 3;

printf("%d %d\n", *p, *(a+3));

}

运行结果:

1 2

3 3

5 4

此例中指向数组a与指针变量p的指向变化情况见图10-6。

注意,第2个printf语句中的"*(++p)",是先使指针p自增加1,再取指针p值作"*"运算,它的含义等价于第3个printf语句中的"*++p"。而"*(p--)"是先取指针p值作"*" 运算,再使指针p自减减1。

用指针方式实现对数组的访问是很方便的,可以使源程序更紧凑、更清晰。

10.3.2 指针基本运算

对于指针的运算有三种:指针与正整数的加减运算、两个指针的关系运算,以及两个指针的减法运算。

1. 指针与正整数的加减运算

当指针p指向数组中的元素时,n为一个正整数,则表达式:

p+n

表示:指针p所指向当前元素之后的第n个元素。而表达式:

p-n

表示:指针p所指向当前元素之前的第n个元素。

最常见的指针加减运算为p++的含义是:指针加1,指向数组中的下一个元素;p--的含义是:指针减1,指向数组中的前一个元素。

指针与整数进行加减运算后,它们的相对关系如图10-7所示。

由于指针p所指的具体对象不同,所以对指针与整数进行加减运算时,C语言会根据所指的不同对象计算出不同的放大因子,以保证正确操作实际的运算对象。对于字符型,放大因子为1;对于整型,放大因子为2;对于长整型,放大因子为4;对于双精度浮点型,放大因子为8。不同数据类型的放大因子等于一个该数据类型的变量所占用的内存单元数

例10-8:编程将str1复制到str2中。

#include

#include

main( )

{ char str1[80], str2[80], *p1, *p2;

printf("Enter string 1:");

gets(str1);

p1=str1;

p2=str2;

while ( (*p2=*p1) != '\0' ) /* 指针p1的内容送到指针p2 */

{ p1++; p2++; } /* 指针p1和p2分别向后移动1个字符*/

printf("String 2:");

puts(str2);

}

程序中的关键是while语句,"(*p2=*p1)!='\0'"的含义是:先将指针p1的内容送到指针p2的内容中,即进行两个指针内容的赋值,然后再判断所赋值的字符是否是串结束标记'\0',如果不是串结束标记,则执行循环继续进行字符复制;如果是串结束标记,则退出循环,完成串复制。

对于上面程序中的while循环,是可以进行优化的。优化后的循环可以如下:

优化一:

while ( *p2 = *p1 )

{ p1++; p2++; }

优化二:

while ( *p2++ = *p1++ ) ; /* 循环体为空*/

2. 两个指针的关系运算

只有当两个指针指向同一个数组中的元素时,才能进行关系运算。

当指针p和指针q指向同一数组中的元素时,

则:

p

为1;反之为0。

P>q 当p所指的元素在q所指的元素之后时,表达式的值为1;反之为0。

p==q 当p和q指向同一元素时,表达式的值为1;反之为0。

p!=q 当p和q不指向同一元素时,表达式的值为1;反之为0。

任何指针p与NULL进行"p==NULL"或"p!=NULL"运算均有意义,"p==NULL"的含义是当指针p为空时成立,"p!=NULL"的含义是当p不为空时成立。

不允许两个指向不同数组的指针进行比较,因为这样的判断没有任何实际的意义。

例10-9:编写程序将一个字符串反向。

#include

main( )

{ char str[50], *p, *s, c;

printf("Enter string:");

gets(str);

p=s=str; /* 指针p和s指向str */

while ( *p )

p++; /* 找到串结束标记'\0' */

p--; /* 指针回退一个字符,保证反向后的字符串有串结束

标记'\0',指针p指向字符串中的最后一个字符*/

while ( s

{ c = *s; /* 交换两个指针所指向的字符*/

*s++ = *p; /* 串前面的指针s向后(+1)移动*/

*p-- = c; /* 串后面的指针p向前(-1)移动*/

}

puts(str);

}

3. 两个指针的减法运算

只有当两个指针指向同一数组中的元素时,才能进行两个指针的减法运算,否则,没有意义。

当两个指针指向同一数组中的元素时,p-q表示指针p和q所指对象之间的元素数量。利用这一意义,可以求出一个字符串的长度。

例10-10:编写程序求字符串的长度。

#include

main( )

{ char str[50], *p=str;

printf("Enter string:");

gets(str);

while ( *p )

p++; /* 找到串结束标记'\0'。退出循环时p指向'\0' */

printf("String length=%s\n", p-str ); /* 指向同一字符数组的两个指针进行减法运算,求出串长*/

}

10.3.3 通过指针引用二维数组中的元素

在C语言中,二维数组是按行优先的规律转换为一维线性存放在内存中的,因此,可以通过指针访问二维数组中的元素。

如果有:int a[M][N];

则将二维数组中的元素a[i][j]转换为一维线性地址的一般公式是:

线性地址=a+i×M+j

其中:a为数组的首地址,M和N分别为二维数组行和列的元素个数。

若有:int a[4][3], *p;

p = &a[0][0];

则二维数组a的数据元素在内存中存储顺序及地址关系如图10-8所示。

这里,a表示二维数组的首地址;a[0]表示0行元素的起始地址,a[1]表示1行元素的起始地址,a[2]和a[3]分别表示2行和3行元素的起始地址。

数组元素a[i][j]的存储地址是:&a[0][0]+i*n+j。

我们可以说:a和a[0]是数组元素a[0][0]的地址,也是0行的首地址。a+1和a[1]是数组元素a[1][0]的地址,也是1行的首地址。

由于a 是二维数组,经过两次下标运算[ ]之后才能访问到数组元素。所以根据C语言的地址计算方法,a要经过两次*操作后才能访问到数组元素。这样就有:*a是a[0]的内容,即数组元素a [0][0]的地址。**a是数组元素a[0][0]。a[0]是数组元素a[0][0]的地址,*a[0]是素组元素a[0][0]。

例10-11:给定某年某月某日,将其转换成这一年的第几天并输出。

此题的算法很简单,若给定的月是i,则将1、2、3、……、i-1月的各月天数累加,再加上指定的日。但对于闰年,二月的天数29天,因此还要判定给定的年是否为闰年。为实现这一算法,需设置一张每月天数列表,给出每个月的天数,考虑闰年非闰年的情况,此表可设置成一个2行13列的二维数组,其中第1行对应的每列(设1~12列有效)元素是平年各月的天数,第2行对应的是闰年每月的天数。程序中使用指针作为函数day_of_year的形式参数。

#include

main( )

{ static int day_tab[2][13]={

{0,31,28,31,30,31,30,31,31,30,31,30,31},

{0,31,29,31,30,31,30,31,31,30,31,30,31} };

int y, m, d;

scanf("%d%d%d", &y, &m, &d);

printf("%d\n", day_of_year(day_tab,y,m,d) ); /* 实参为二维数组名*/ }

day_of_year(day_tab,year,month,day)

int *day_tab; /* 形式参数为指针*/

int year, month, day;

{ int i, j;

i = (year%4==0&&year%100!=0) || year%400==0;

for ( j=1; j

day += *( day_tab+i*13+j );

/* day_tab+i*13+j:对二维数组中元素进行地址变换*/

return(day);

}

由于C语言对于二维数组中的元素在内存中是按行存放的,所以

在函数day_of_year 中要使用公式"day_tab+i*13+j"计算main函数的day_tab中元素对应的地址

C语言之指针、数组和函数2009-02-09 17:57基本解释

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是一个函数指针,它指向一个参数为空的函数,这个函数返回一个整数。

相关文档