第8章 善于利用指针

学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。

正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:

 

#include <stdio.h>

 

int main ()

{

   int  var1;

   char var2[10];

 

   printf("var1 变量的地址: %p\n", &var1  );

   printf("var2 变量的地址: %p\n", &var2  );

 

   return 0;

}

当上面的代码被编译和执行时,它会产生下列结果:

var1 变量的地址: 0x7fff5cc109d4

var2 变量的地址: 0x7fff5cc109de

通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。

8.1什么是指针?

指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。

指针变量声明的一般形式为:type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

int    *ip;    /* 一个整型的指针 */

double *dp;    /* 一个 double 型的指针 */

float  *fp;    /* 一个浮点型的指针 */

char   *ch;     /* 一个字符型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

8.2指针变量

8.2.1 使用指针变量的例子

#include <stdio.h>

int main()

{ int a=100,b=10;

//定义整型变量a,b,并初始化

int *pointer_1,*pointer_2;

//定义指向整型数据的指针变量pointer_1, pointer_2

pointer_1=&a; //把变量a的地址赋给指针变量pointer_1

       pointer_2=&b;   //把变量b的地址赋给指针变量pointer_2

printf("a=%d,b=%d\n",a,b); //输出变量a和b的值

printf("*pointer_1=%d,*pointer_2=%d\n",*pointer_1,*pointer_2);

//输出变量a和b的值

return 0;

}

运行结果:

 

上面变量对应的关系,如图:

 

 

注:定义指针变量时,左侧应有类型名,否则就不是定义指针变量。也称为基类型

8.2.2 怎样定义指针变量

定义指针的一般形式为:

类型名 *指针变量名;

如:

int *pointer_1;

注意:左端的int是在定义指针变量时必须指定的"基类型".换个说法就是类型名一定要有,否则是错误的定义。

如:

*pointer_1; //企图定义pointer_1为指针变量。出错

int *pointer_1; //正确,必须指定指针变量的基类型

 

说明:在定义指针变量时要注意一下几点:

(1) 指针变量前面的"*"表示该变量的类型为指针型变量。

(2) 在定义指针变量时必须制定基类型(即类型名)

一个变量的指针的含义包括两个方面,一是以存储单元编号表示的地址(如编号为2000的字节),一是它指向的存储单元的数据类型(如int,char,float等)。

(3)如何表示指针类型

指向整型数据的指针类型表示为"int*",读作"指向int的指针"或简称"int指针"

(4) 指针变量中只能存放地址(地址),不要讲一个整数赋给一个指针变量。如:

*pointer_1 = 100; //pointer_1是指针变量,100是整数,不合法的赋值

8.2.3 怎样引用指针变量

(1) 给指针变量赋值。如:

p = &a; //把a的地址赋给指针变量a

(2) 引用指针变量指向的变量

如果已执行"p = &a;",即指针变量p指向了整型变量a,则

printf("%d",*p);

作用:以整数形式输出指针变量p所指向的变量的值,即变量a的值

注:一定要带'*',否则输出地址

也可以用指针的形式对a重新赋值

*p = 1;

表示将1赋值给p当前p指向的变量,因为p指向变量a,则相当于把1赋值给a,即和"a = 1"等价。

(3) 引用指针变量的值。如:

printf("%o",p);

作用:以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a.

printf("%o",&a);   <==> printf("%o",p); //两者等价

例:

#include <stdio.h>

int main()

{

int *p,a;

a = 100;

p = &a;

printf("%o\n",p);

printf("%o\n",&a);

 

}

 

要熟练掌握两个有关的运算符:

(1) &取地址运算符。&a是变量a的地址。

(2) * 指针运算符(或称“间接访问”运算符),*p代表指针变量p指向的对象。

 

#include <stdio.h>

int main()

{ int *p1,*p2,*p,a,b;     //p1,p2的类型是int *类型

printf("please enter two integer numbers:");

       scanf("%d,%d",&a,&b);                       //输入两个整数

p1=&a;       //使p1指向变量a

p2=&b;       //使p2指向变量b

if(a<b)       //如果a<b

{ p=p1;p1=p2;p2=p;}   //使p1与p2的值互换

printf("a=%d,b=%d\n",a,b);   //输出a,b

printf("max=%d,min=%d\n",*p1,*p2); //输出p1和p2所指向的变量的值

return 0;

}

运行结果:

 

指针在过程中的变化:

 

 

8.2.4 指针变量作为函数参数

例:对输入的两个整数按大小顺序输出。现用函数处理,而且用指针类型的数据作函数参数。

#include <stdio.h>

void swap(int *p1,int *p2);    //对swap函数的声明

int main()

{

int a,b;

int *pointer_1,*pointer_2; //定义两个int *型的指针变量

printf("please enter a and b:");

scanf("%d,%d",&a,&b);  //输入两个整数

pointer_1=&a;   //使pointer_1指向a

       pointer_2=&b;                 //使pointer_2指向b

if(a<b) swap(pointer_1,pointer_2); //如果a<b,调用swap函数

printf("max=%d,min=%d\n",a,b);  //输出结果

return 0;

}

 

void swap(int *p1,int *p2)   //定义swap函数

{ int temp;

temp=*p1;     //使*p1和*p2互换

*p1=*p2;

*p2=temp;

}

运行结果:

 

指针变量pointer_1 pointer_2的变量过程

 

 

注:要注意区分以下三种形式,可自己调试,增加理解

void swap(int *p1,int *p2)//定义swap函数

{ int temp;

temp=*p1;//使*p1和*p2互换

*p1=*p2;

*p2=temp;

}

void swap(int *p1,int *p2)

{ int *temp;

*temp=*p1;

      *p1=*p2;

*p2=*temp;

}

void swap(int x,int y)

{ int temp;

temp=x;

      x=y;

y=temp;

}

下面有一个问题需要注意,也可以直接跳过,如果想提升可以理解,以理解为主,不需要死记硬背。

指针变量作为函数参数

函数的调用可以(而且只可以)得到一个返回值(即函数值),而使用指针变量作参数,可以得到多个变化了的值。如果不用指针变量是难以做到这一点的。要善于利用指针法。

如果想通过函数调用得到n个要改变的值,可以这样做:

 

1.在主调函数中设n个变量,用n个指针变量指向它们;

 

2.设计一个函数,有n个指针形参。在这个函数中改变这n个形参的值;

 

3.在主调函数中调用这个函数,在调用时将这n个指针变量作实参,将它们的值,也就是相关变量的地址传给该函数的形参;

 

3.在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值;

 

5.主调函数中就可以使用这些改变了值的变量。

例:输入3个整数a,b,c,要求按由大到小的顺序将它们输出。用函数实现。

#include <stdio.h>

int main()

{ void exchange(int *q1, int *q2, int *q3); //函数声明

int a,b,c,*p1,*p2,*p3;

printf("please enter three numbers:");

scanf("%d,%d,%d",&a,&b,&c);

p1=&a;p2=&b;p3=&c;

exchange(p1,p2,p3);

printf("The order is:%d,%d,%d\n",a,b,c);

return 0;

}

 

void exchange(int *q1, int *q2, int *q3) //将3个变量的值交换的函数

{ void swap(int *pt1, int *pt2);  //函数声明

if(*q1<*q2) swap(q1,q2);  //如果a<b,交换a和b的值

if(*q1<*q3) swap(q1,q3);  //如果a<c,交换a和c的值

if(*q2<*q3) swap(q2,q3);  //如果b<c,交换b和c的值

}

 

void swap(int *pt1, int *pt2)   //交换2个变量的值的函数

{ int temp;

temp=*pt1;    //交换*pt1和*pt2变量的值

*pt1=*pt2;

*pt2=temp;

}

运行结果:

 

 

8.3 通过指针引用数组

8.3.1 数组元素的指针

数组元素的指针就是素组元素的地址。

int a[10]={1,3,5,7,9,11,13,15,17,19};  //定义a为包含10个整型数据的数组

int *p;         //定义p为指向整型变量的指针变量

p=&a[0];        //把a[0]元素的地址赋给指针变量p

引用数组元素的方式

(1) 下标法

(2) 指针法

p=&a[0]; //p的值是a[0]的地址

等价于

p=a; //p的值是数组a首元素(即a[0])的地址

注:程序中的数组名不代表整个数组,只代表数组首元素的地址。

以下是指针变量的初始化方式:

(1) 在定义后再单独进行初始化

int *p;

p=&a[0]; //不应写成*p=&a[0];

(2) 在定义时直接进行初始化

int *p=&a[0];

等价于

int *p=a;

以上三种的作用:将a数组首元素(即a[0])的地址赋值给指针变量p(而不是赋给*p)

8.3.2 在引用数组元素时指针的运算

在指针已指向一个数组元素时,可以对指针进行以下运算:

1.加一个整数(用+或+=),如p+1,表示指向同一数组中的下一个元素;

 

2.减一个整数(用-或-=),如p-1,表示指向同一数组中的上一个元素;

 

3.自加运算,如p++,++p;

 

4.自减运算,如p--,--p。

 

两个指针相减,如p1-p2(p1和p2都指向同一数组中的元素时才有意义),结果为两个地址之差除以数组元素的长度。

注:两个地址不能相加,如p1+p2是无实际意义的。

 

说明:

*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。

p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址

[]实际上是变址运算符,即将a[i]按a+i计算地址,然后找出此地址单元中的值。

例:有一个整型数组a,有10个元素,要求输出数组中的全部元素。

//下标法

#include <stdio.h>

int main()

{ int a[10];

int i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

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

printf("%d ",a[i]);

//数组元素用数组名和下标表示

printf("%\n");

return 0;

}

//通过数组名计算数组元素地址,找出元素的值

#include <stdio.h>

int main()

{ int a[10];

int i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

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

printf("%d ",*(a+i));

//通过数组名和元素序号计算元素地址找到该元素

printf("\n");

return 0;

}

//用指针变量指向数组元素

#include <stdio.h>

int main()

{ int a[10];

int *p,i;

printf("please enter 10 integer numbers:");

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

scanf("%d",&a[i]);

for(p=a;p<(a+10);p++)

printf("%d ",*p);

//用指针指向当前的数组元素

printf("\n");

return 0;

}

说明:

第(1)和第(2)种方法执行效率是相同的。C编译系统是将a[i]转换为*(a+i)处理的,即先计算元素地址。因此用第(1)和第(2)种方法找数组元素费时较多。

 

第(3)种方法比第(1)、第(2)种方法快,用指针变量直接指向元素,不必每次都重新计算地址,像p++这样的自加操作是比较快的。这种有规律地改变地址值(p++)能大大提高执行效率。

 

例:通过指针变量输出整型数组a的10个元素。

#include <stdio.h>

int main()

{ int *p,i,a[10];

p=a;    //p指向a[0]  ①

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //输入10个整数给a[0]~a[9]

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

printf("%d ",*p); //想输出a[0]~a[9] ②

printf("\n");

return 0;

}

#include <stdio.h>

int main()

{ int i,a[10],*p=a; //p的初值是a,p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++);

p=a;    //重新使p指向a[0]

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

printf("%d ",*p);

printf("\n");

return 0;

}

运行上面两个代码看看有何区别,造成这种区别的原因是什么?

答:第一种是因为指针已经到了数组的末尾,继续循环则超出了数组的范围,则会直接输出P对应的地址值。如图所示:

 

 

技巧:

设p开始时指向数组a的首元素(即p=a)

(1)

p++; //使p指向下一元素a[1]

*p; //得到下一个元素a[1]的值

(2)

*p++; /*由于++和*同优先级,结合方向自右而左,因此它等价于*(p++)。先引用p的值,实现*p的运算,然后再使p自增1*/

(3)

*(p++); //先取*p值,然后使p加1

*(++p); //先使p加1,再取*p

(4)

++(*p); /*表示p所指向的元素值加1,如果p=a, 则相当于++a[0],若a[0]的值为3,则a[0]的值为4。注意: 是元素a[0]的值加1,而不是指针p的值加1*/

(5)

如果p当前指向a数组中第i个元素a[i],则:

*(p--) //相当于a[i--],先对p进行“*”运算,再使p自减

*(++p) //相当于a[++i],先使p自加,再进行“*”运算

*(--p) //相当于a[--i],先使p自减,再进行“*”运算

8.3.4 用数组名作函数参数

以变量名和数组名作为函数参数的比较

 

 

C语言调用函数时虚实结合的方法都是采用“值传递”方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。

 

注意:实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。

 

例:将数组a中n个整数按相反顺序存放

#include <stdio.h>

int main()

{ void inv(int x[],int n); //inv函数声明

int i,a[10]={3,7,9,11,0,6,7,5,4,2};

printf("The original array:\n");

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

printf("%d ",a[i]); //输出未交换时数组各元素的值

printf("\n");

inv(a,10);    //调用inv函数,进行交换

printf("The array has been inverted:\n");

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

printf("%d ",a[i]); //输出交换后数组各元素的值

printf("\n");

return 0;

}

void inv(int x[],int n)  //形参x是数组名

{ int temp,i,j,m=(n-1)/2;

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

{ j=n-1-i;

temp=x[i]; x[i]=x[j]; x[j]=temp; //把x[i]和x[j]交换

}

return;

}

#include <stdio.h>

int main()

{ void inv(int *x,int n);

int i,a[10]={3,7,9,11,0,6,7,5,4,2};

printf("The original array:\n");

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

printf("%d ",a[i]);

printf("\n");

inv(a,10);

printf("The array has been inverted:\n");

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

printf("%d ",a[i]);

printf("\n");

return 0;

}

 

void inv(int *x,int n)   //形参x是指针变量

{ int *p,temp,*i,*j,m=(n-1)/2;

i=x; j=x+n-1; p=x+m;

for(;i<=p;i++,j--)

{ temp=*i; *i=*j; *j=temp;} //*i与*j交换

return;

}

以上两种方式所得结果是一致的,只是实现的形式有区别。所得结果都为下图所示:

说明:以下的形式要认识,以下四种都是等价的

 

 

例:将数组a中n个整数按相反顺序存放,用指针变量作实参。

#include <stdio.h>

int main()

{ void inv(int *x,int n); //inv函数声明

int i,arr[10],*p=arr;  //指针变量p指向arr[0]

printf("The original array:\n");

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

scanf("%d",p);  //输入arr数组的元素

printf("\n");

p=arr;    //指针变量p重新指向arr[0]

inv(p,10);    //调用inv函数,实参p是指针变量

printf("The array has been inverted:\n");

for(p=arr;p<arr+10;p++)

printf("%d ",*p);

printf("\n");

return 0;

}

 

void inv(int *x,int n)         //定义inv函数,形参x是指针变量

{ int *p,m,temp,*i,*j;

m=(n-1)/2;

i=x;j=x+n-1;p=x+m;

for(;i<=p;i++,j--)

{ temp=*i;*i=*j;*j=temp;}

return;

}

注:如果用指针变量作实参,必须先使指针变量有确定值,指向一个已定义的对象。

 

例:用指针方法对10个整数按由大到小顺序排序。(选择排序法)

//形参是数组

#include <stdio.h>

int main()

{ void sort(int x[],int n); //sort函数声明

int i,*p,a[10];

p=a;     //指针变量p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //输入10个整数

p=a; //指针变量p重新指向a[0]

sort(p,10);   //调用sort函数

for(p=a,i=0;i<10;i++)

{ printf("%d ",*p); //输出排序后的10个数组元素

p++;

}

printf("\n");

return 0;

}

 

void sort(int x[],int n)//x是形参数组名

{ int i,j,k,t;

for(i=0;i<n-1;i++)

{ k=i;

for(j=i+1;j<n;j++)

if(x[j]>x[k]) k=j;

if(k!=i)

{ t=x[i]; x[i]=x[k]; x[k]=t;}

}

}

//形参是指针

#include <stdio.h>

int main()

{ void sort(int x[],int n); //sort函数声明

int i,*p,a[10];

p=a;     //指针变量p指向a[0]

printf("please enter 10 integer numbers:");

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

scanf("%d",p++); //输入10个整数

p=a; //指针变量p重新指向a[0]

sort(p,10);   //调用sort函数

for(p=a,i=0;i<10;i++)

{ printf("%d ",*p); //输出排序后的10个数组元素

p++;

}

printf("\n");

return 0;

}

 

void sort(int *x,int n) //形参x是指针变量

{ int i,j,k,t;

for(i=0;i<n-1;i++)

{ k=i;

for(j=i+1;j<n;j++)

if(*(x+j)>*(x+k)) k=j; //*(x+j)就是x[j],其他亦然

if(k!=i)

{ t=*(x+i); *(x+i)=*(x+k); *(x+k)=t;}

}

}

 

8.3.5 通过指针引用多维数组

注:这章节了解即可,如果想提升自己,不仅需要理解还需要会使用。

理解以下这张图便可了解多维数组:

 

 

说明:我们在这里说明一下,在计算机底层是不存在二维数组这种存储方式的,都是以一维数组为基础逻辑化出二维数组,而一维数组不仅是逻辑化更是物理上也是可以实现的。总的来说,二维数组是一维数组的逻辑化,本质上二维数组还是一维数组。只是为了方便人们理解,才把二维数组分为行和列的方式显示。

 

如果用一个指针变量pt来指向此一维数组:

int (*pt)[4];

//表示pt指向由4个整型元素组成的一维数组,此时指针变量pt的基类型是由4个整型元素组成的一维数组

 

例:输出二维数组的有关数据(地址和元素的值)。

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

printf("%d,%d\n",a,*a);    //0行起始地址和0行0列元素地址

printf("%d,%d\n",a[0],*(a+0));   //0行0列元素地址

printf("%d,%d\n",&a[0],&a[0][0]);  //0行起始地址和0行0列元素地址

printf("%d,%d\n",a[1],a+1);   //1行0列元素地址和1行起始地址

printf("%d,%d\n",&a[1][0],*(a+1)+0); //1行0列元素地址

printf("%d,%d\n",a[2],*(a+2));   //2行0列元素地址

printf("%d,%d\n",&a[2],a+2);   //2行起始地址

printf("%d,%d\n",a[1][0],*(*(a+1)+0)); //1行0列元素的值

printf("%d,%d\n",*a[2],*(*(a+2)+0)); //2行0列元素的值

return 0;

}

结果如下:

 

 

说明:要注意看懂以上的格式,区分各种形式的联系和区别。如a和*a在输出上是等价的

 

例:有一个3×4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

int *p;       //p是int *型指针变量

for(p=a[0];p<a[0]+12;p++)   //使p依次指向下一个元素

{ if((p-a[0])%4==0) printf("\n"); //p移动4次后换行

              printf("%4d",*p);                           //输出p指向的元素的值

}

printf("\n");

return 0;

}

运行结果:

 

例:输出二维数组任一行任一列元素的值。

 

#include <stdio.h>

int main()

{ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};  //定义二维数组a并初始化

int (*p)[4],i,j;   //指针变量p指向包含4个整型元素的一维数组

p=a;     //p指向二维数组的0行

printf("please enter row and colum:");

scanf("%d,%d",&i,&j); //输入要求输出的元素的行列号

printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));   //输出a[i][j]的值

return 0;

}

 

#include <stdio.h>

int main()

{ int a[4]={1,3,5,7};  //定义一维数组a,包含4个元素

int (*p)[4];   //定义指向包含4个元素的一维数组的指针变量中

p=&a;    //使p指向一维数组

printf("%d\n",(*p)[3]); //输出a[3],输出整数7

return 0;

}

 

比较:

① int a[4];(a有4个元素,每个元素为整型)

② int (*p)[4];

第②种形式表示(*p)有4个元素,每个元素为整型。也就是p所指的对象是有4个整型元素的数组,即p是指向一维数组的指针,见图8.24。应该记住,此时p只能指向一个包含4个元素的一维数组,不能指向一维数组中的某一元素。p的值是该一维数组的起始地址。虽然这个地址(指纯地址)与该一维数组首元素的地址相同,但它们的基类型是不同的。

 

指向由m个元素组成的一维数组的指针变量

要注意指针变量的类型,从“int (*p)[4];”可以看到,p的类型不是int *型,而是int (*)[4]型,p被定义为指向一维整型数组的指针变量,一维数组有4个元素,因此p的基类型是一维数组,其长度是16字节。“*(p+2)+3”括号中的2是以p的基类型(一维整型数组)的长度为单位的,即p每加1,地址就增加16个字节(4个元素,每个元素4个字节),而“*(p+2)+3”括号外的数字3,不是以p的基类型的长度为单位的。由于经过*(p+2)的运算,得到a[2],即&a[2][0],它已经转化为指向列元素的指针了,因此加3是以元素的长度为单位的,加3就是加(3×4)个字节。虽然p+2和*(p+2)具有相同的值,但由于它们所指向的对象的长度不同,因此(p+2)+3和*(p+2)+3的值就不相同了。

一维数组名可以作为函数参数,多维数组名也可作函数参数。

用指针变量作形参,以接受实参数组名传递来的地址。可以有两种方法:

① 用指向变量的指针变量;

② 用指向一维数组的指针变量。

例8:有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。

#include <stdio.h>

int main()

{ void average(float *p,int n);

void search(float (*p)[4],int n);

float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};

average(*score,12);  //求12个分数的平均分

search(score,2);   //求序号为2的学生的成绩

return 0;

}

 

void average(float *p,int n)  //定义求平均成绩的函数

{ float *p_end;

float sum=0,aver;

p_end=p+n-1;

//n的值为12时,p_end的值是p+11,指向最后一个元素

for(;p<=p_end;p++)

sum=sum+(*p);

aver=sum/n;

printf("average=%5.2f\n",aver);

}

 

void search(float (*p)[4],int n)

//p是指向具有4个元素的一维数组的指针

{ int i;

printf("The score of No.%d are:\n",n);

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

              printf("%5.2f ",*(*(p+n)+i));

printf("\n");

}

运行结果:

 

 

注:实参与形参如果是指针类型,应当注意它们的基类型必须一致。不应把int *型的指针(即数组元素的地址)传给int (*)[4] 型(指向一维数组)的指针变量,反之亦然。

例:在上例的基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩。

#include <stdio.h>

int main()

{ void search(float (*p)[4],int n); //函数声明

float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};

//定义二维数组函数score

search(score,3);    //调用search函数

return 0;

}

 

void search(float (*p)[4],int n)

//形参p是指向包含4个float型元素的一维数组的指针变量

{ int i,j,flag;

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

{ flag=0;

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

if(*(*(p+j)+i)<60) flag=1;

//*(*(p+j)+i)就是score[j][i]

if(flag==1)

{ printf("No.%d fails,his scores are:\n",j+1);

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

printf("%5.1f ",*(*(p+j)+i));

//输出*(*(p+j)+i)就是输出score[j][i]的值

printf("\n");

}

}

}

运行结果:

 

 

 

8.4 通过指针引用字符串

8.4.1 字符串的引用方式

(1)用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明“%s”输出该字符串。

(2)用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。

 

例:定义一个字符数组,在其中存放字符串″I love China!″,输出该字符串和第8个字符。

#include <stdio.h>

int main()

{ char string[]="I love China!"; //定义字符数组sting

printf("%s\n",string);  //用%s格式声明输出string,可以输出整个字符串

       printf("%c\n",string[7]);         //用%c格式输出一个字符数组元素

return 0;

}

运行结果:

 

 

例:通过字符指针变量输出一个字符串。

#include <stdio.h>

int main()

{ char *string="I love China!"; //定义字符指针变量string并初始化

printf("%s\n",string);  //输出字符串

return 0;

}

运行结果:

 

 

在C语言中只有字符变量,没有字符串变量。

char *string="I love China!";

等价于

char *string;  //定义一个char *型变量

string=″I love China!″;

//把字符串第1个元素的地址赋给字符指针变量string

注:string被定义为一个指针变量,基类型为字符型。它只能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把″I love China!″这些字符存放到string中(指针变量只能存放地址),也不是把字符串赋给*string。只是把″I love China!″的第1个字符的地址赋给指针变量string。

 

可以对指针变量进行再赋值,string=″I am a student.″;  //对指针变量string重新赋值

可以通过字符指针变量输出它所指向的字符串,printf(″%s\n″,string); //%s可对字符串进行整体的输入输出

 

说明:%s是输出字符串时所用的格式符,在输出项中给出字符指针变量名string,则系统会输出string所指向的字符串第1个字符,然后自动使string加1,使之指向下一个字符,再输出该字符……如此直到遇到字符串结束标志′\0′为止。注意,在内存中,字符串的最后被自动加了一个′\0′。

 

例:将字符串a复制为字符串b,然后输出字符串b。

//用数组的方式输出

#include <stdio.h>

int main()

{ char a[]="I am a student.",b[20]; //定义字符数组

int i;

for(i=0;*(a+i)!='\0';i++)

*(b+i)=*(a+i);  //将a[i]的值赋给b[i]

*(b+i)='\0';    //在b数组的有效字符之后加'\0'

printf("string a is:%s\n",a);//输出a数组中全部有效字符

printf("string b is:");

for(i=0;b[i]!='\0';i++)

printf("%c",b[i]); //逐个输出b数组中全部有效字符

printf("\n");

return 0;

}

 

//用指针变量

#include <stdio.h>

int main()

{ char a[]="I am a boy.",b[20],*p1,*p2;

p1=a;p2=b;

//p1,p2分别指向a数组和b数组中的第一个元素

for(;*p1!='\0';p1++,p2++)  //p1,p2每次自加1

*p2=*p1;

//将p1所指向的元素的值赋给p2所指向的元素

*p2='\0';   //在复制完全部有效字符后加'\0'

printf("string a is:%s\n",a); //输出a数组中的字符

printf("string b is:%s\n",b); //输出b数组中的字符

return 0;

}

 

8.4.2 字符指针作函数参数

例:用函数调用实现字符串的复制

(1) 用字符数组名作为函数参数

#include <stdio.h>

int main()

{ void copy_string(char from[], char to[]);

char a[]="I am a teacher.";

char b[]="You are a student.";

printf("string a=%s\nstring b=%s\n",a,b);

printf("copy string a to string b:\n");

copy_string(a,b);  //用字符数组名作为函数实参

printf("\nstring a=%s\nstring b=%s\n",a,b);

return 0;

}

 

void copy_string(char from[], char to[])    //形参为字符数组

{ int i=0;

while(from[i]!='\0')

{ to[i]=from[i]; i++;}

to[i]='\0';

}

运行结果:

 

 

(2) 用字符型指针变量作实参

#include <stdio.h>

int main()

{ void copy_string(char from[], char to[]); //函数声明

char a[]="I am a teacher.";  //定义字符数组a并初始化

char b[]="You are a student."; //定义字符数组b并初始化

       char *from=a,*to=b; //from指向a数组首元素,to指向b数组首元素

printf("string a=%s\nstring b=%s\n",a,b);

printf("copy string a to string b:\n");

copy_string(from,to); //实参为字符指针变量

printf("\nstring a=%s\nstring b=%s\n",a,b);

return 0;

}

void copy_string(char from[], char to[])   //形参为字符数组

{ int i=0;

while(from[i]!='\0')

{ to[i]=from[i]; i++;}

to[i]='\0';

}

运行结果:

 

 

说明:指针变量from的值是a数组首元素的地址,指针变量to的值是b数组首元素的地址。它们作为实参,把a数组首元素的地址和b数组首元素的地址传递给形参数组名from和to(它们实质上也是指针变量)。其他与程序(1)相同。

(3) 用字符指针变量作形参和实参

#include <stdio.h>

int main()

{ void copy_string(char *from, char *to);

char *a="I am a teacher.";  //a是char*型指针变量

char b[]="You are a student."; //b是字符数组

char *p=b;  //使指针变量p指向b数组首元素

printf("string a=%s\nstring b=%s\n",a,b); //输出a串和b串

printf("copy string a to string b:\n");

copy_string(a,p); //调用copy_string函数,实参为指针变量

printf("\nstring a=%s\nstring b=%s\n",a,b); //输出改变后的a串和b串

return 0;

}

 

void copy_string(char *from, char *to) //定义函数,形参为字符指针变量

{ for(;*from!='\0';from++,to++)

{ *to=*from;}

*to='\0';

}

运行结果:

 

 

函数void copy_string(char *from, char *to)还有以下等价几种,只需记住一种即可,其它做了解。最好的情况当然是全都会用。

void copy_string(char *from, char *to)

{ for(;(*to++=* from++)!='\0';);

//或for(;*to++=* from++;);

}

void copy_string(char *from, char *to)

{ while((*to=*from)!='\0')

//或while(*to=*from)

{ to++; from++;}

}

void copy_string(char *from, char *to)

{ while(*from!='\0')

//或while(*from) ,因为'\0'的ASCII码为0

*to++=*from++;

*to='\0';

}

void copy_string(char *from, char *to)

{ while((*to++=*from++)!='\0');

//或while(*to++=*from++)

}

void copy_string(char from[],char to[])

{ char *p1, *p2;

p1=from;p2=to;

while((*p2++=*p1++)!='\0');

}

 

字符指针作为函数参数时,实参与形参的类型有以下几种对应关系:

 

 

8.4.3 使用字符指针变量和字符数组的比较

 

 

例:改变指针变量的值。

#include <stdio.h>

int main()

{ char *a="I love China!";

a=a+7;   //改变指针变量的值,即改变指针变量的指向

printf("%s\n",a); //输出从a指向的字符开始的字符串

return 0;

}

运行结果:

 

 

 

#include <stdio.h>

int main()

{ char str[]={"I love China!"};

str=str+7;

printf("%s\n",str);

return 0;

}

说明:

(1)指针变量的值是可以改变的,而字符数组名代表一个固定的值(数组首元素的地址),不能改变。

(2)指针变量a的值是可以变化的。printf函数输出字符串时,从指针变量a当时所指向的元素开始,逐个输出各个字符,直到遇'\0'为止。而数组名虽然代表地址,但它是常量,它的值是不能改变的。

8.5 指向函数的指针

8.5.1 什么是函数指针

如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段内存空间有一个起始地址,也称为函数的入口地址。每次调用函数时都从该地址入口开始执行此段函数代码。

函数名就是函数的指针,它代表函数的起始地址。

可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。例如: int (*p)(int,int);

定义p是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时,指针变量p的类型用int (*)(int,int)表示。

8.5.2 用函数指针变量调用函数

例:用函数求整数a和b中的大者

//(1)通过函数名调用函数

#include <stdio.h>

int main()

{ int max(int,int); //函数声明

int a,b,c;

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

c=max(a,b);  //通过函数名调用max函数

printf("a=%d\nb=%d\nmax=%d\n",a,b,c);

return 0;

}

 

int max(int x,int y)  //定义max函数

{ int z;

if(x>y) z=x;

else z=y;

return(z);

}

 

(2) 通过指针变量调用它所指向的函数

#include <stdio.h>

int main()

{ int max(int,int); //函数声明

int (*p)(int,int); //定义指向函数的指针变量p

int a,b,c;

p=max;   //使p指向max函数

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

c=(*p)(a,b);  //通过指针变量调用max函数

printf("a=%d\nb=%d\nmax=%d\n",a,b,c);

return 0;

}

int max(int x,int y)  //定义max函数

{ int z;

if(x>y)z=x;

else z=y;

return(z);

}

运行结果:

 

 

8.5.3 怎样定义和使用指向函数的指针变量

定义指向函数的指针变量的一般形式为:

类型名 (*指针变量名)(函数参数表列)

如:

int (*p)(int,int);

说明:

(1) 定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。

(2)  如果要用指针调用函数,必须先使指针变量指向该函数。

(3) 在给函数指针变量赋值时,只须给出函数名而不必给出参数。

(4) 用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。

(5) 对指向函数的指针变量不能进行算术运算,如p+n,p++,p--等运算是无意义的。

(6) 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。

例:输入两个整数,然后让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。

#include <stdio.h>

int main()

{ int max(int,int); //函数声明

int min(int x,int y); //函数声明

int (*p)(int,int); //定义指向函数的指针变量

int a,b,c,n;

printf("please enter a and b:");

scanf("%d,%d",&a,&b);

printf("please choose 1 or 2:");

scanf("%d",&n); //输入1戓2

if(n==1) p=max; //如输入1,使p指向max函数

else if (n==2) p=min; //如输入2,使p指向min函数

c=(*p)(a,b);  //调用p指向的函数

printf("a=%d,b=%d\n",a,b);

if(n==1) printf("max=%d\n",c);

else printf("min=%d\n",c);

return 0;

}

 

int max(int x,int y)

{ int z;

if(x>y) z=x;

else z=y;

return(z);

}

 

int min(int x,int y)

{ int z;

if(x<y) z=x;

else z=y;

return(z);

}

运行结果:

 

OR

 

8.5.4 用指向函数的指针作函数参数

 

 

例:有两个整数a和b,由用户输入1,2或3。如输入1,程序就给出a和b中的大者,输入2,就给出a和b中的小者,输入3,则求a与b之和。

#include <stdio.h>

int main()

{ int fun(int x,int y, int (*p)(int,int)); //fun函数声明

       int max(int,int);                //max函数声明

int min(int,int);   //min函数声明

int add(int,int);   //add函数声明

int a=34,b=-21,n;

printf("please choose 1,2 or 3:");

scanf("%d",&n);   //输入1,2或3之一

if(n==1) fun(a,b,max);  //输入1时调用max函数

else if(n==2) fun(a,b,min); //输入2时调用min函数

else if(n==3) fun(a,b,add); //输入3时调用add函数

return 0;

}

 

int fun(int x,int y,int (*p)(int,int)) //定义fun函数

{      int result;

result=(*p)(x,y);

       printf("%d\n",result);             //输出结果

}

 

 

int max(int x,int y) //定义max函数

{ int z;

if(x>y) z=x;

else z=y;

printf("max=" );

       return(z);            //返回值是两数中的大者

}

 

int min(int x,int y) //定义min函数

{ int z;

if(x<y) z=x;

else z=y;

printf("min=");

return(z);  //返回值是两数中的小者

}

 

int add(int x,int y) //定义add函数

{ int z;

z=x+y;

printf("sum=");

return(z);  //返回值是两数之和

}

运行结果:不同选择有不同的答案

 

 

 

 

 

 

 

 

8.6 返回指针值的函数

定义返回指针值的函数的一般形式为:

类型名 *函数名(参数表列);

解释:

一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。其概念与以前类似,只是返回的值的类型是指针类型而已。如:

int *a(int x,int y);

a是函数名,调用它以后能得到一个int*型(指向整型数据)的指针,即整型数据的地址。x和y是函数a的形参,为整型。

注:在“*a”两侧没有括号,在a的两侧分别为*运算符和()运算符。而()优先级高于*,因此a先与()结合,显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。

例:有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。

#include <stdio.h>

int main()

{ float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};

//定义数组,存放成绩

float *search(float (*pointer)[4],int n); //函数声明

float *p;

int i,k;

printf("enter the number of student:");

scanf("%d",&k); //输入要找的学生的序号

printf("The scores of No.%d are:\n",k);

p=search(score,k); //调用search函数,返回score[k][0]的地址

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

printf("%5.2f\t",*(p+i)); //输出score[k][0]~score[k][3]的值

printf("\n");

return 0;

}

 

float *search(float (*pointer)[4],int n)

//形参pointer是指向一维数组的指针变量

{ float *pt;

pt=*(pointer+n); //pt的值是&score[k][0]

return(pt);

}

运行结果:

 

 

例:对上例题,找出其中有不及格的课程的学生及其学生号。

#include <stdio.h>

int main()

{ float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};

//定义数组,存放成绩

float *search(float (*pointer)[4]); //函数声明

float *p;

int i,j;

for(i=0;i<3;i++)    //循环3次

{ p=search(score+i);

//调用search函数,如有不及格返回score[i][0]的地址,否则返回NULL

if(p==*(score+i))

//如果返回的是score[i][0]的地址,表示p的值不是NULL

{ printf("No.%d score:",i);

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

printf("%5.2f  ",*(p+j));

//输出score[i][0]~score[i][3]的值

printf("\n");

              }

}

       return 0;

}

 

float *search(float (*pointer)[4])

//定义函数,形参pointer是指向一维数组的指针变量

{ int i=0;

float *pt;

pt=NULL; //先使pt的值为NULL

for(;i<4;i++)

if(*(*pointer+i)<60) pt=*pointer;

              //如果有不及格课程,使pt指向score[i][0]

return(pt);

}

运行结果:

 

8.7 指针数组和多重指针

8.7.1 什么是指针数组

定义一维指针数组的一般形式为

类型名 *数组名[数组长度];

如:

int *p[4];

一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。

指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。

 

例:将若干字符串按字母顺序(由小到大)输出。

 

 

 

#include <stdio.h>

#include <string.h>

int main()

{ void sort(char *name[],int n);  //函数声明

void print(char *name[],int n); //函数声明

char *name[]={"Follow me","BASIC",

       "Great Wall","FORTRAN","Computer design"};

//定义指针数组,它的元素分别指向5个字符串

int n=5;

       sort(name,n);     //调用sort函数,对字符串排序

print(name,n); //调用print函数,输出字符串

return 0;

}

 

void sort(char *name[],int n)   //定义sort函数

{ char *temp;

int i,j,k;

for(i=0;i<n-1;i++)   //用选择法排序

{ k=i;

for(j=i+1;j<n;j++)

if(strcmp(name[k],name[j])>0) k=j;

if(k!=i)

{ temp=name[i]; name[i]=name[k]; name[k]=temp;}

}

}

 

void print(char *name[],int n) //定义print函数

{ int i;

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

printf("%s\n",name[i]);

//按指针数组元素的顺序输出它们所指向的字符串

}

运行结果:

 

 

在了解了指针数组的基础上,需要了解指向指针数据的指针变量,简称为指向指针的指针。

 

 

例:使用指向指针数据的指针变量。

#include <stdio.h>

int main()

{ char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};

char **p;

int i;

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

{ p=name+i;

printf("%s\n",*p);

}

return 0;

}

运行结果:

 

 

说明:

(1) p是指向char*型数据的指针变量,即指向指针的指针。在第1次执行for循环体时,赋值语句“p=name+i;”使p指向name数组的0号元素name[0],*p是name[0]的值,即第1个字符串首字符的地址,用printf函数输出第1个字符串(格式符为%s)。执行5次循环体,依次输出5个字符串。

 

(2) 指针数组的元素也可以不指向字符串,而指向整型数据或实型数据等。

 

例:有一个指针数组,其元素分别指向一个整型数组的元素,用指向指针数据的指针变量,输出整型数组各元素的值。

#include <stdio.h>

int main()

{ int a[5]={1,3,5,7,9};

int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};

int **p,i;    //p是指向指针型数据的指针变量

p=num;    //使p指向num[0]

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

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

p++;

}

printf("\n");

return 0;

}

运行结果:

 

8.7.3 指针数组作main函数的形参

 

 

 

 

int main(int argc,char *argv[])

{ while(argc>1)

{ ++argv;

printf("%s\n", *argv);

--argc;

}

return 0;

}

int main(int argc,char *argv[])

{ while(argc-->1)

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

return 0;

}.

 

 

 

 

8.8 动态内存分配与指向它的指针变量

8.8.1 什么是内存的动态分配

 

 

8.8.2 怎样建立内存的动态分配

1. 用malloc函数开辟动态存储区

函数原型为

void *malloc(unsigned int size);

作用:在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值(即“返回值”)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的第一个字节。如:

malloc(100);              //开辟100字节的临时分配域,函数值为其第1个字节的地址

指针的基类型为void,即不指向任何类型的数据,只提供一个纯地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。

 

2.用calloc函数开辟动态存储区

函数原型为

void *calloc(unsigned n, unsigned size);

作用:在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。

p=calloc(50,4);  //开辟50×4个字节的临时分配域,把首地址赋给指针变量p

用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的第一个字节的指针;如果分配不成功,返回NULL。

 

3.用realloc函数重新分配动态存储区

函数原型为

void *realloc(void *p,unsigned int size);

作用:如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配。

realloc(p,50); //将p所指向的已分配的动态空间改为50字节

 

用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。

 

4.用free函数释放动态存储区

函数原型为

void free(void *p);

作用:释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。

free(p);  //释放指针变量p所指向的已分配的动态空间

free函数无返回值。

 

说明:以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include <stdlib.h>”指令把stdlib.h头文件包含到程序文件中。

 

8.8.3 void指针类型

C 99允许使用基类型为void的指针类型。可以定义一个基类型为void的指针变量(即void*型变量),它不指向任何类型的数据。在将它的值赋给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。

int *pt;

pt=(int *)mcaloc(100); //mcaloc(100)是void *型,把它转换为int *型

注:不要把“指向void类型”理解为能指向“任何的类型”的数据,而应理解为“指向空类型”或“不指向确定的类型”的数据。

例:建立动态数组,输入5个学生的成绩,另外用一个函放数检查其中有无低于60分的,输出不合格的成绩。

#include <stdio.h>

#include <stdlib.h>    //程序中用了malloc函数,应包含stdlib.h

int main()

{ void check(int *);    //函数声明

int *p1,i;      //p1是int型指针

p1=(int *)malloc(5*sizeof(int)); //开辟动态内存区,将地址转换成int *型,然后放在p1中

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

              scanf("%d",p1+i);                   //输入5个学生的成绩

check(p1);     //调用check函数

return 0;

}

 

void check(int *p)     //定义check函数,形参是int*指针

{ int i;

printf("They are fail:");

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

              if(p[i]<60) printf("%d ",p[i]);        //输出不合格的成绩

printf("\n");

}

运行结果:

 

 

有关指针的小结

 

 

 

 

posted @ 2019-06-11 23:23  HOsystem  阅读(477)  评论(0编辑  收藏  举报