内容正文:
《C语言程序设计》
(天津大学出版社)
第7章 指针、函数和数组
7.1 变量的地址和指针
7.2 指针变量
7.3 一维数组和指针
7.4 一维数组和函数
7.5 二维数组和指针
7.6 二维数组和函数
7.7 字符串和指针
7.8 指向函数的指针
7.9 返回指针值的函数
7.10 指针数组和二级指针
7.5 二维数组和指针
7.5.1 二维数组首地址
7.5.2 二维数组元素的地址
7.5.3 二维数组元素的指针变量
7.5.4 指向第一维数组的指针变量
7.5.1 二维数组首地址
同一维数组一样,二维数组的数组名就是二维数组的首地址。
首地址表达了二维数组的基本访问位置。
C语言中,对于数组的实现而言,
多维数组与一维数组是一样的,
也就是说在实现方法上只有一维数组。
因此,其二维数组是作为多个一维数组
来对待的。
例7.18 多维数组分解
int a[3][5];
则可以这样解释:a数组的第一维有3
个元素。每个元素是一维数组,其中
一维数组的元素个数是5个。
如右图:
返回
7.5.2 二维数组元素的地址
可以看到,二维数组的关键问题在于如何表达和计算二维数组元素的地址。找到其地址的目的是访问某个元素。当声明或定义一个二维数组时,其某个元素的地址可以依照如下公式来计算:
某元素的地址=首地址+某元素的行下标*行元素个数+列下标
例7.19 数组元素地址的计算
int a[5][7];
分析:上述定义了5行7列的二维数组(二维数组可以直接理解为一个行乘以列形式的方阵)。
元素a[2][3]的地址=元素a[0][0]的地址+2*7+3=元素a[0][0]的地址+17
注意到上面的地址公式计算中行元素个数为7个。这里我们计算的元素a[2][3]的地址是用来给指针变量用的,假设有个指针变量被赋值为a数组的首地址的话。实际上如果仅仅使用数组访问的时候,我们是无需该地址的。因为a[2][3]这个写法已经表达了该变量。
另外一种方法就是直接取得数组元素a[2][3]的地址:&a[2][3]。
返回
7.5.3 二维数组元素的指针变量
讨论:指针变量如何赋值、指针变量如何访问二维数组中的某个元素。
指针变量的赋值与一维数组完全一致,仅仅数组是二维而已。来看一个例子:
例7.20 用指针访问二维数组中最后一个元素
#include <stdio.h>
main ()
{
int a[3][2]={{0,0},{0,0},{0,0}},*p;
p=a[0];
*(p+2*2+1)=9;
printf ("
%d
",a[2][1]);
}
运行结果:
9
7.5.3 二维数组元素的指针变量
使用指针访问二维数组的步骤:
第一步:定义指针变量与二维数组。
例如:int a[3][2],*p;
第二步:将数组的首地址赋值给指针变量。
例如:p=&a[0][0]或者是p=a[0].
第三步:使用指针来访问二维数组的第i行与第j列的某个元素。
a[i][j]对应*(p+行下标i*行元素个数+列下表j)
7.5.3 二维数组元素的指针变量
例7.21 使用一级指针访问二维数组
#include <stdio.h>
main ()
{
int a[3][2]={{0,1},{10,11},{20,21}},*p;
int i,j;
p=a[0];
for (i=0;i<3;i++)
for (j=0;j<2;j++)
printf (" %d ",*(p+i*2+j));
printf ("
");
}
运行结果:
0 1 10 11 20 21
Press any key to continue
返回
7.5.4 指向第一维数组的指针变量
数组与指针的一个重要用法是:指向多维数组的指针。指向数组的指针说明如下:
类型名 (*指针名)[数组长度];
解释为:说明了一个指针,该指针指存储了二维数组的第一维的地址,其第二维的元素个数为数组长度。比如:int (*p)[3];解释为:p是一个指针,该指针未来记录一个二维数组的第一维的地址,其第二维的元素为int类型,第二维的元素个数为3。
可见,事实上指向一个二维数组的第一维的指针(上面的指针说明形式)实际上就是:定义了一个存放二维数组的首地址的指针变量。
7.5.4 指向第一维数组的指针变量
例7.22 若有定义int(*pt)[3];,则下列说法正确的是(2010年3月全国计算机等级考试二级C试题选择题第27题)
A)定义了基类型为int的三个指针变量
B)定义了基类型为int的具有三个元素的指针数组pt
C)定义了一个名为*pt、具有三个元素的整型数组
D)定义了一个名为pt的指针变量,它可以指向每行有三个整数元素的二维数组
分析:根据题意,pt实际上是一个整型指针,而且是一个指向指针的指针,它指向的是一个有3个元素的整型数组的数组的首地址,因此相对于二维数组的数组名。所以,选D。
可见,指针与二维数组名属于同等级别,因此在赋值的时候只需要将数组名直接赋值给指针即可。
7.5.4 指向第一维数组的指针变量
例7.23 若有定义语句:char s[3][10],(*k)[3],*p;,则以下赋值语句正确的是(2011年3月全国计算机等级考试二级C试题选择题第28题)
A)p=s; B)p=k; C)p=s[0]; D)k=s;
分析:选项A的含义是将二维数组的首地址赋值给了一个指针,选项B的含义是将一维数组的指针赋值给了一个指向变量的指针,选项C的含义是将一个一维数组首地址赋值给了一个指针,选项D的含义是将二维数组赋值给了指向一维数组的指针。选C。
7.6 二维数组和函数
7.6.1 二维数组名做函数参数
7.6.2 指向第一维数组的指针变量做函数参数
7.6.1 二维数组名做函数参数
将二维数组名作为函数的参数来传递,关键在于函数的参数必须定义为指针类型或者是二维数组。此时,由于数组名代表了数组的首地址,则函数中对数组元素的访问就改变了调用函数中数组元素的值。来看这样一个例子:
例7.24 有以下程序(2011年9月全国计算机等级考试二级C试题选择题第28题)
#include<stdio.h>
#define N 4
void fun(int a[ ][N],int b[ ])
{
int i;
for(i=0;i<N;i++) b[i]=a[i][i]-a[i][N-1-i];
}
main()
{
int x[N][N]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}},y[N],i;
fun(x,y);
for(i=0;i<N;i++) printf("%d,",y[i]);
printf("
");
}
程序运行后的输出结果是:
A.-12,-3,0,0, B.-3,-1,1,3,
C.,0,1,2,3, D.-3,3,-3,-3,
7.6.1 二维数组名做函数参数
分析:fun函数的关键语句是b[i]=a[i][i]-a[i][N-1-i];该语句完成了将4*4矩阵中对角线上的元素值减去本行的第N-1-i个元素的值,并将它保存到数组b中。具体功能如下图:
从上面的例子可以发现,函数fun(int a[ ][N],int b[ ])的参数声明中,a数组的写法为a[][N],这是一个类似指针的用法,但是不能作为指针来用(即不能写成*a[N]的形式)。当访问二维数组元素的时候必须写成a[i][j]的方式。选B。
返回
7.6.2 指向第一维数组的指针变量做函数参数
指向第一维数组的指针变量(实际上就是二维数组的首地址)也可以作为函数的参数。由于进入函数时对每个参数产生一个副本,如果将指向一维数组的指针变量作为函数的指针,则会产生该指针参数的副本。由于指针本身的特点是通过指针访问对应的地址,所以即便是产生地址副本也能够完全访问相应地址所对应的内存。
那么一般有两种方式来将指向第一维数组的首地址作为参数。第一种方式是形参说明为数组的指针,实参为数组名。第二种方式是形参说明为数组的指针,实参为指向第一维数组的指针变量。比如数组a和函数fun声明如下,且函数的参数为指向第一维数组的指针变量。
int a[2][9],(*p)[9];
int fun (int (*q)[9]);这个语句的等价语句为int fun (int (*)[9]);
则调用函数fun时就可以使用两种方式:
第一种方式作为函数的参数时,调用函数的语句为:
fun(a);.
第二种方式作为函数的参数时,调用函数的语句为:
p = a;
fun(p);
使用第二种方式时,注意到p是数组类型的指针变量,类型与形参的类型int (*q)[9]相同。赋值表达式p=a;使p指向二维数组a的第0行(即a数组的首地址,那么(p+1)表示第一行)。
7.7 字符串和指针
7.7.1 字符串的引用方式
7.7.2 字符指针做函数参数
7.7.3 字符指针变量和字符数组的比较
7.7.1 字符串的引用方式
使用指针来引用字符串的方法与使用指针引用数组变量基本一致。必须看到引用字符串时的优势,可以使用标准库函数相对比较方便地操作整个字符串。注意到使用指针引用数组时,不能方便地操作整个数组。如果需要逐个操作数组元素则通常需要使用循环来进行。
例7.25 使用指针操作字符串
#include <stdio.h>
#include <string.h>
main ()
{
char s[10]="hello";
char *p=s;
printf ("%s
",s);
strcpy (p,"HI");
printf ("%s",s);
printf ("
");
}
运行结果:
hello
HI
Press any key to continue
从上面的例子可以发现,熟练使用系统库函数对于字符串的操作完全可以简化。当然,也可以使用指针来逐个操作字符串中的每个字符。
7.7.1 字符串的引用方式
例7.26 有以下程序(注:字符 a 的 ASCII 码值为 97)。(2011年9月全国计算机等级考试二级C选择题22题)
#include <stdio.h>
main()
{
char *s={"abc"};
do{
printf("%d",*s%10);
++s;
}while(*s);
printf("
");
}
程序运行后的输出结果是
A.abc B.789 C.7890 D.979898
分析:选B。
注意:例7.26中字符串s的实际占用空间为4个字节(包含’\0’),而字符串的串长为3.
返回
7.7.2 字符指针做函数参数
同前述数组,指向字符串首地址的字符指针也可以做函数参数。下面来看一个例子:
例7.27 指针作为函数的参数(2011年9月全国计算机等级考试二级C试题选择题35题)
#include<stdio.h>
#include<string.h>
void fun(char *u,int n)
{
char x,*y1,*y2;
y1=u;
y2=u+n-1;
while(y1<y2)
{
x=*y1;
*y1=*y2;
*y2=x;
y1++;
y2--;
}
}
main()
{
char a[]="1,2,3,4,5,6";
fun(a,strlen(a));
puts(a);
}
程序运行后的输出结果是:
A.654321 B.115611 C.153525 D.123456
答案:A
7.7.2 字符指针做函数参数
如图所示:while循环体内的执行过程
返回
7.7.3 字符指针变量和字符数组的比较
字符指针变量与字符数组是有差别的,这两个概念必须明确。首先,由例7.4我们可以知道指针只占用4个字节的存储空间,字符指针也是这样。而字符数组则占用数组长度乘以数组字符类型大小的存储空间。即:
字符指针占用 4字节存储空间
字符数组占用的存储空间字节数 = (数组长度+1)sizeof(char)
实际上,字符数组占用的存储空间字节数就等于数组长度加一。那是因为字符类型只占用一个字节的存储空间,上式sizeof(char)就等于1。并且注意到,(数组长度+1)中的那个”1”就是字符的结束符’\0’。来看一个例子:
例7.28 字符指针变量与字符数组各自占用的空间
#include <stdio.h>
main ()
{
char *p,a[10];
printf ("字符指针类型变量占用的空间为:%d",sizeof(p));
printf ("
字符指针所指向的内存占用的空间为:%d",sizeof(*p));
printf ("
字符数组变量占用的空间为:%d",sizeof(a));
printf ("
");
}
运行结果:
字符指针类型变量占用的空间为:4
字符指针所指向的内存占用的空间为:1
字符数组变量占用的空间为:10
7.7.3 字符指针变量和字符数组的比较
第二个问题就是:字符指针变量若不赋值,则其指向的空间是不确定的,如果强行访问则必将导致内存泄露。但是字符数组若不赋值,系统仍然为其分配确定的存储空间,即使不予赋值(数组中具体存放的内容不确定)仍然可以访问,只是将数组的内容作为字符而已。注意到,如果不对数组进行赋值而强行访问的话,则该数组没有结束符标志’\0’,访问的时候就不能使用string.h库中的标准字符串函数访问。那么为了防止下标越界,访问的时候使用循环即可。
例7.29 字符指针不赋值的后果
#include <stdio.h>
main ()
{
char *p,a[10];
printf ("
字符数组的内容为:%s",a);
printf ("字符指针类型变量所表示的内容为:%c",*p);
printf ("
");
}
该例需上机测试。
7.8 指向函数的指针
7.8.1 函数指针的定义和使用
7.8.2 用函数指针变量调用函数
7.8.3 用指向函数的指针作函数参数
7.8.1 函数指针的定义和使用
函数指针:指针不仅能表示变量、表示数组与作为函数的参数,而且可以定义为指向函数的指针。指向函数的指针就是函数指针。函数指针是存放函数入口地址的指针变量。一个函数的入口地址由函数的函数名表示,他是函数在内存中的首条语句的入口地址。由于函数名是函数的入口地址,因此如果将函数名赋值给一个指针变量则该指针称为指向该函数的函数指针。
7.8.1 函数指针的定义和使用
在定义函数指针的时候,函数指针的类型必须与函数名的类型一致。函数指针的定义形式如下:
类型说明 (*标识符)(参数表)
比如定义一个返回值为整型函数的指针可以写成:int (*p)(int a);。该语句可以说明为:p是一个指针,该指针指向具有一个整型参数且返回值为整型的函数。
使用函数指针也比较简单,即将函数名赋值给同类型的函数指针即可。来看一个例子:
例7.30 函数指针的定义与使用
#include <stdio.h>
void fun (int a)
{
printf ("
这是fun函数,带入的参数是:%d",a);
}
main ()
{
void (*p) (int a);
p = fun;
(*p) (5);
printf ("
");
}
运行结果:
这是fun函数,带入的参数是:5
返回
7.8.2 用函数指针变量调用函数
函数指针的用法类似于变量指针的用法。注意到程序运行的时候是将程序装入内存,则函数是存在内存入口地址的,因此若将函数名赋给指针变量,在类型匹配的前提下,通过相应的指针变量访问内存是完全可行的。并且可以通过指针的变化来调用不同的函数。
例7.31 使用函数指针实现对不同函数的访问。
#include <stdio.h>
int sub (int x,int y)
{
return x-y;
}
int add (int x,int y)
{
return x+y;
}
main ()
{
int (*p) (int a,int b);
int a,b,result;
printf("
请输入两个整数(两个整数之间用一个空格隔开):");
scanf("%d%d",&a,&b);
if (a>b)
{
p = sub; //语句1
result = (*p)(a,b);
}
else
{
p = add; //语句2
result = (*p)(a,b);
}
printf ("
最终的结果为:%d",result);
printf ("
");
}
运行结果1:
请输入两个整数(两个整数之间用一个空格隔开):1 3
最终的结果为:4
运行结果2:
请输入两个整数(两个整数之间用一个空格隔开):3 1
最终的结果为:2
分析:
程序功能:如果输入的整数a大于b则调用减法函数,否则调用加法函数。上述代码仅仅是作为一个调用函数指针的实例,通常情况下并不会这样使用。在实际的使用当中会使用到的方式就是将函数指针作为函数的参数,只有这样才能真正的做到“按需”灵活调用必须的函数。注意到,例子7.31的缺点在于语句1和语句2,我们使用了两次赋值,这完全等于使用原来的函数,函数指针基本上成了个临时变量。所以下面我们介绍使用的方法指向函数的指针作为函数的参数。
返回
7.8.3 用指向函数的指针作函数参数
使用当中,通常会用指向函数的指针作函数参数。这样做是希望根据应用来调用所需要的函数,那么只要将所调用的函数作为另外一个函数的参数就可以解决问题。完全可以根据实际的需求的灵活调用。这里重写例子7.31如下:
例7.32 将函数指针作为参数使用重写例7.31
#include <stdio.h>
int sub (int x,int y)
{
return x-y;
}
int add (int x,int y)
{
return x+y;
}
//定义一个处理器函数,其第三个参数定义为函数指针
int proccessor(int x,int y,int (*p) (int a,int b))
{
return (*p)(x,y);
}
main ()
{
int a,b;
printf("
请输入两个整数(两个整数之间用一个空格隔开):");
scanf("%d%d",&a,&b);
printf("
最终的结果为:%d",
a>b?proccessor(a,b,sub):proccessor(a,b,add));
printf ("
");
}
运行结果1:
请输入两个整数(两个整数之间用一个空格隔开):1 3
最终的结果为:4
运行结果2:
请输入两个整数(两个整数之间用一个空格隔开):3 1
最终的结果为:2
分析:
很明显,这个代码就大大简化了,完全实现了例7.31的效果,而且代码非常简洁。那么这里对于函数指针作为函数参数的用法仅仅作一般性介绍。
7.8.3 用指向函数的指针作函数参数
例7.33 请将以下程序中的函数声明语句补充完整。(2009年3月全国计算机等级考试二级C语言填空题第12题)
#include <stdio.h>
int _____;
main()
{
int x, y, (*p)();
scanf("%d%d", &x, &y);
p=max;
printf("%d
", (*p)(x,y));
}
int max(int a, int b)
{
return(a>b?a:b);
}
答案:max(int a, int b)
分析:
从指针p的调用方式(*p)(x,y)与变量x,y为整型知空白处应当填写一个函数的定义且函数有两个整型参数,再由printf("%d
", (*p)(x,y));的格式字符串%d知且该函数应有一个整数返回值,再由p=max语句知该函数名为max。综合分析与下面max的函数定义即不难得出答案为:max(int a, int b)。那么,假设给出的输入为: 1 3时,输出为:3。
7.9 返回指针值的函数
函数可以返回任意类型的值,那么其中一个特殊的值就是函数可以返回指针值。函数可以返回的那个指针值其实就是函数返回一个地址。因此返回指针值的函数就是返回地址的函数。大家心里必须充分清楚一点,只需要知道返回的值是什么地址即可,无需知道具体地址值的数值是多少。
那么返回是地址的函数做什么用途呢?我们可以直接理解为:指针函数就是一个函数!该函数的功能可以返回某个地址。
指针函数的说明形式:
类型说明 *标识符 (参数);
7.9 返回指针值的函数
例7.34 指针函数及其用法
#include <stdio.h>
int *p (int a,int b)
{
return a>b?&a:&b;
}
main ()
{
int a,b;
printf ("
请输入两个整数:");
scanf("%d%d",&a,&b);
printf (“
输入的两个数中较大
的那个是:%d",*(p(a,b)));
printf ("
");
}
运行结果:
请输入两个整数:3 5
输入的两个数中较大的那个是:5
例7.35 打印每个字符串中从小写字母’s’开头到字符串尾的所有字符
#include <stdio.h>
char *find_s (char *p)
{ char *s=p;
while (*s!='\0' && *s!='s')s++;
return s;
}
main ()
{ char s[3][20]={"sfirst","cdegsecond","no"};
int i;
for (i=0;i<3;i++)
printf ("%s
",find_s(s[i]));
}
运行结果:
sfirst
second
7.10 指针数组和二级指针
7.10.1 指针数组的定义和使用
7.10.2 指向指针数据的指针
7.10.1 指针数组的定义和使用
指针数组:数组的每个元素都是指针。指针数组的定义如下:
类型名 *数组名 [数组长度];
例如可以定义一个整型指针数组:int *s[10]。该数组解释为:s是一个具有10个元素的指针数组,每个元素可以存储一个指向整型变量的指针(如果被赋值的话就可能是指向某个整型变量的指针)。
例7.36 有以下程序(2010年3月全国计算机等级考试二级C语言填空题第10题)
#include <stdio.h>
main()
{
int a[]={1,2,3,4,5,6},*k[3],i=0;
while(i<3)
{
k[i]=&a[2*i];
printf("%d",*k[i]);
i++;
}
printf ("
");
}
程序运行后的输出结果是:________
答案:135
分析:例7.36语句int a[]={1,2,3,4,5,6},*k[3],i=0; 中声明了一个指针数组k,其说明方式为:int *k[3],解释为:k是一个具有三个整型指针元素的指针数组。在while循环内部,将数组a下标为2*i的元素所在的地址赋值给指针数组k的第i个元素。然后在语句4使用打印语句访问该指针数组。实际上就是通过指针数组间接地访问了数组a。
返回
7.10.2 指向指针数据的指针
指针还有一个使用方式就是多级指针。典型的多级指针就是二级指针。多级指针就是指向指针的指针。所谓“指向指针的指针”实际上就是:存储指针变量地址的变量(只不过这个变量也是一个指针变量而已)。下面来看二级指针的说明:
类型声明 **指针名 ;
比如定义一个整型的二级指针:int **p;解释为:p是一个二级指针,其第一级指针*p指向某个整数变量的地址。而**p则表达了该整型变量。当然,这必须是p在被赋值的情况下。来看一个例子:
例7.37 二级指针的声明与访问
#include <stdio.h>
main()
{
int *pc,**p;
int i;
i = 5;
pc = &i;
p = &pc;
printf("
变量i的值为:%d",i);
*pc = 6;
printf("
经过一级指针pc的操作后,变量i的值为:%d",i);
**p = 7;
printf("
经过二级指针p的操作后,变量i的值为:%d",i);
printf ("
");
}
运行结果:
变量i的值为:5
经过一级指针pc的操作后,变量i的值为:6
经过二级指针p的操作后,变量i的值为:7
谢谢观看
$$