第八章
主要内容
8.1 地址和指针的概念
8.2 变量的指针和指向变量的指针变量
8.3 通过指针引用数组
8.4 通过指针字符串
8.5 指向函数的指针
8.6 返回指针值的函数
8.7 指针数组和多重指针
8.8 动态内存分配与指向它的指针变量
8.1地址和指针的概念内存区的每一个字节有一个编号,这就是
,地址,。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。
1、按变量地址存取变量值的方式称为,直接访问,方式
printf( ″%d ″,i);
scanf( ″%d ″,&i);
k=i+j;
2,另一种存取变量值的方式称为,间接访问,的方式。
即,将变量i的地址存放在另一个变量中。
在C语言中,指针是一种特殊的变量,它是存放地址的。
一个变量的地址称为该变量的,指针,。
例如,地址 2000是变量i的指针。如果有一个变量专门用来存放另一变量的地址(即指针),则它称为,指针变量,。上述的 i_pointer就是一个指针变量。
指针和指针变量的定义:
8.2 变量的指针和指向变量的指针变量
8.2,1 怎样定义指针变量定义指针变量的一般形式为基类型 *指针变量名;
下面都是合法的定义:
float *pointer_3;
char *pointer_4;
可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。
例如:
pointer_1=&i;
pointer_2=&j;
在定义指针变量时要注意两点:
(1)指针变量前面的,*”,表示该变量的类型为指针型变量。
例,float *pointer_1;
指针变量名是 pointer_1,而不是 * pointer_1 。
(2) 在定义指针变量时必须指定基类型。
需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。下面的赋值是错误的 ∶
float a;
int * pointer_1;
pointer_1=&a;
在对指针变量赋值时需要注意两点:
⑴ 指针变量中只能存放地址(指针),不要将一个整数赋给一个指针变量。
例,* pointer_1=100; /* pointer_1是指针变量,100是整数,不合法 */
(2) 赋给指针变量的变量地址不能是任意的类型,而只能是与指针变量的基类型具有相同类型的变量的地址。
在引用指针变量时,可能有三种情况:
⑴给指针变量赋值。如:
p=&a;
⑵ 引用指针变量的值。如:
printf(“%o”,p);
⑶ 引用指针变量指向的变量。
有关的两个运算符:
(1) & 取地址运算符。 &a是变量 a的地址。
(2) * 指针运算符 (或称“间接访问”运算符),*p
是指针变量 p指向的对象的值。
8.2.2 怎样引用指针变量例 8.1 通过指针变量访问整型变量
#include <stdio.h>
void main ( )
{ int a,b;
int *pointer_1,*pointer_2;
a=100;b=10;
pointer_1=&a; /*把变量a的地址赋给
pointer_1 */
pointer_2=&b; /*把变量b的地址赋给
pointer_2 */
printf( ″%d,%d \n ″,a,b);
printf( ″%d,%d \n ″,*pointer_1,*pointer_2);
}
例 8,2 输入a和b两个整数,按先大后小的顺序输出
a和b。
#include <stdio.h>
void main()
{ int *p 1,*p 2,*p,a,b;
scanf( ″%d,%d ″,&a,&b);
p 1=&a;p2=&b;
if(a<b)
{p=p1;p1=p2;p2=p;}
printf( ″a =%d,b =%d \n \n ″,a,b);
printf( ″max=%d,min=%d \n ″,*p 1,*p 2);
}
运行情况如下:
5,9 ↙
a=5,b=9
max=9,min=5
当输入a=5,b=9时,由于a<b,
将p1和p2交换。交换前的情况见图
(a),交换后见图(b)。
8.2,3 指针变量作为函数参数例 8,3 对输入的两个整数按大小顺序输出
#include <stdio.h>
void main()
{ void swap( int *p1,int *p2) ;
int a,b;
int *pointer_1,*pointer_2;
scanf( ″%d,%d ″,&a,&b);
pointer_1 =&a; pointer_2 =&b;
if(a<b) swap( pointer_1,pointer_2 );
printf( ″\n%d,%d \n ″,a,b);
}
void swap( int *p1,int *p2)
{ int temp;
temp= *p 1;
*p1= *p2;
*p2= temp;
}
例 8.4输入 3个整数a,b,c,要求按大小顺序将它们输出。
用函数实现改变这 3个变量的值。
#include <stdio.h>
void main()
{ void exchange( int *q 1,int *q 2,int *q 3) ;
int a,b,c,*p1,*p2,*p3;
scanf( ″%d,%d,%d ″,&a,&b,&c);
p1=&a;p2=&b;p3=&c;
exchange (p1,p2,p3);
printf( ″\n%d,%d,%d\n ″,a,b,c);
}
void exchange( int *q1,int *q2,int *q3)
{ void swap( int *pt1,int *pt2) ;
if( *q1< *q2) swap(q1,q2);
if( *q1< *q3) swap(q1,q3);
if( *q2< *q3= swap(q2,q3);
}
void swap( int *pt1,int *pt2)
{ int temp;
temp= *pt1;
*pt1= *pt2;
*pt2= temp;
}
8.3 通过指针引用数组一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓 数组元素的指针就是数组元素的地址 。
8.3.1 数组元素的指针可以 用一个指针变量指向一个数组元素。
例如,int a[10]; (定义a为包含10个整型数据的数组 )
int *p; (定义p为指向整型变量的指针变量 )
p=& a[0]; (把a[0]元素的地址赋给指针变量p )
也就是使p指向a数组的第0号元素 。
C语言规定在 指针指向数组元素时,可以对指针进行以下运算:
加一个整数 (用 +或 +=),如 p+1
减一个整数 (用 -或 -=),如 p-1
自加运算,如 p++,++p
自减运算,如 p--,--p
两个指针相减,如 p1-p2 (只有 p1和 p2都指向同一数组中的元素时才有意义 )。
8.3.2 指针的运算分别说明如下:
(1)如果指针变量 p已指向数组中的一个元素,则p+1
指向同一数组中的下一个元素,p -1指向同一数组中的上一个元素。
(2) 如果 p原来指向 a[0],执行 ++p后 p的值改变了,在 p的原值基础上加 d,这样 p就指向数组的下一个元素 a[1]。
(3) 如果p的初值为&a [0 ],则p+i和a+i就是数组元素a[i]的地址,或者说,它们指向a数组的第i个元素 。
(4) *(p+i)或 *(a+i)是p+i或a+i所指向的数组元素,即a[i]。
(5) 如果指针变量 p1和 p2都指向同一数组,如执行 p2-p1,
结果是两个地址之差除以数组元素的长度。
8.3,3 通过指针引用数组元素引用一个数组元素,可以用:
(1) 下标法,如a[i]形式;
(2) 指针法,如 *(a+i)或 *(p+i) 。
其中a是数组名,p是指向数组元素的指针变量,其初值p=a。
例 8.5 输出数组中的全部元素假设有一个a数组,整型,有10个元素。要输出各元素的值有三种方法:
(1)下标法
#include <stdio.h>
void main()
{ int a[10];
int i;
for(i=0;i<10;i++)
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
printf( ″%d ″,a[i]);
}
(2) 通过数组名计算数组元素地址,找出元素的值。
#include <stdio.h>
void main()
{ int a[10];
int i;
for(i=0;i<10;i++ )
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
printf( ″%d ″,*(a+i));
}
(3) 用指针变量指向数组元素。
#include <stdio.h>
void main()
{ int a[10];
int *p,i;
for(i=0;i<10;i++)
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(p=a;p<(a+10);p++)
printf( ″%d ″,*p);
}
例 8.6 通过指针变量输出a数组的10个元素。
#include <stdio.h>
void main()
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++ )
scanf( ″%d ″,p++);
printf( ″\n ″);
for(i=0;i<10;i++,p++ )
printf( ″%d ″,*p);
}
程序运行情况:
1 2 3 4 5 6 7 8 9 0↙
22153 234 0 0 30036 25202 11631 8259 8237 28483
显然输出的数值并不是a数组中各元素的值
#include <stdio.h>
void main()
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++)
scanf( ″%d ″,p++);
printf( ″\n ″);
p=a;
for(i=0;i<10;i++,p++ )
printf( ″%d ″,*p);
} 程序运行情况:1 2 3 4 5 6 7 8 9 0↙
1 2 3 4 5 6 7 8 9 0
8.3.4 用数组名作函数参数在第 7章中介绍过可以用数组名作函数的参数。
如,void main()
{if( int arr[],int n) ;
int array[10];
┇
f( array,10);
┇
}
void f (int arr[ ],int n )
{
┇
}
例 8.7 将数组a中n个整数按相反顺序存放
#include <stdio.h>
void 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 in verted,\n ″);
for(i=0;i<10;i++)
printf ( ″%d,″,a[i]);
printf ( ″\n ″);
}
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;
}
return;
}
运行情况如下:
The original array:
3,7,9,11,0,6,7,5,4,2
The array has been inverted:
2,4,5,7,6,0,11,9,7,3
#include <stdio.h>
void 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 in verted,\n ″ );
for(i=0;i<10;i++)
printf ( ″%d,″,a[i]);
printf ( ″\n ″); }
对刚才的程序可以作一些改动。将函数 inv中的形参x改成指针变量。
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--)
{t emp= *i; *i= *j; *j= temp;}
return;
}
如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况:
(1) 形参和实参都用数组名,如:
void main() void f ( int x [ ],int n)
{ int a[10]; {
… …
f (a,10); }
}
(2) 实参用数组名,形参用指针变量。如:
void main() void f ( int *x,int n)
{ int a[10]; {
… …
f (a,10); }
}
(3)实参形参都用指针变量。例如:
void main() void f( int *x,int n)
{ int a[10],*p=a; {
┇ ┇
f( p,10); }
}
(4) 实参为指针变量,形参为数组名。如:
void main() void f( int x[ ],int n)
{int a[10],*p=a; {
┇ ┇
f( p,10); }
}
#include <stdio.h>
void main()
{ void inv( int *x,int n) ;
int i,arr[10],*p=arr;
printf( ″The original array:\n ″);
for(i=0;i<10;i++,p++)
scanf( ″%d ″,p);
printf( ″\n ″);
p=arr;
inv(p,10); /* 实参为指针变量 */
printf( ″The array has been inverted,\n ″);
for(p=arr;p<arr+10;p++ )
printf( ″%d ″,*p);
printf( ″\n ″);
}
例 8,8 用实参指针变量改写例 8.7。
void inv( int *x,int n)
{ int p,m,temp,*i,*j;
m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--)
{t emp= *i; *i= *j; *j= temp;}
return;
}
例 8,9 用选择法对10个整数按由大到小顺序排序
#include <stdio.h>
void main()
{ void sort( int x[ ],int n) ;
int *p,i,a[ 10];
p=a;
for(i=0;i<10;i++)
scanf( ″%d ″,p++);
p=a;
sort(p,10);
for(p=a,i=0;i<10;i++)
{printf( ″%d ″,*p);p++;}
}
void sort( int x [ ],int n)
{ 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;}
}
}
8.3.5 通过指针引用多维数组用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。
1,多维数组元素的地址先回顾一下多维数组的性质,可以认为二维数组是“数组的数组”,例,
定义 int a[ 3][ 4] ={{1,3,5,7},{9,11,
13,15},{17,19,21,23} };
则二维数组 a是由 3个一维数组所组成的。设二维数组的首行的首地址为2000,则表 示 形 式 含义 地 址
a 二维数组名,指向一维数组
a[0],即 0行首地址
2000
a[0],
*(a+0),
*a
0行 0列元素地址 2000
a+1,&a[ 1] 1行首地址 2008
a[ 1],*(a+1) 1行 0列元素 a[1][0]的地址 2008
A[1]+2,
*(a+1)+2,
&a[1][2]
1行 2列元素 a[1][2] 的地址 2012
*(a[1]+2),
*(*(a+1)+2),
a[1][2]
1行 2列元素 a[ 1][ 2]的值 元素值为 13
2,指向多维数组元素的指针变量
(1) 指向数组元素的指针变量例 8.10 用指向元素的指针变量输出二维数组元素的值
#include <stdio.h>
void main()
{ int a [3][4]={ 1,3,5,7,9,11,13,15,17,19,21,23};
int *p;
for(p=a[0];p<a[0]+12;p++)
{if((p-a[0])%4==0)
printf( ″\n ″);
printf( ″%4d ″,*p); }
}
运行结果如下:
1 3 5 7
9 11 13 15
19 21 23
(2) 用指向由m个元素组成的一维数组的指针变量例 8.11 输出二维数组任一行任一列元素的值
#include <stdio.h>
void main ( )
{ int a[3][4]={ 1,3,5,7,9,11,
13,15,17,19,21,23};
int ( *p)[4],i,j;
p=a;
scanf( ″ i=%d,j=%d ″,&i,&j);
printf( ″a[%d,%d]=%d\n ″,i,
j,*( *(p+i)+j));
}
运行情况如下:
i=1,j=2 ↙
a[1,2]=13
3,用指向数组的指针作函数参数例 8.12 有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。这个题目是很简单的。只是为了说明用指向数组的指针作函数参数而举的例子。用函数 average求总平均成绩,用函数 search
找出并输出第i个学生的成绩。
#include <sydio.h>
void 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的学生的成绩 */
}
void average( float *p,int n)
{ float *p _end;
float sum=0,aver;
p_end=p+n-1;
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));
}
程序运行结果如下:
average=82.25
The score of No.2 are:
90,00 99,00 100,00 98,00
例 8.1 3 在上题基础上,查找有一门以上课程不及格的学生,打印出他们的全部课程的成绩。
#include <stdio.h>
void main()
{ void search( float (*p)[ 4],int n); /*函数声明 */
float score[3][4]={{65,57,70,60},{58,87,
90,81},{90,99,100,98}};
search( score,3);
}
void search( float (*p)[ 4],int n)
{ 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;
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 ));
printf( ″\n ″);
}
}
}
程序运行结果如下:
No.1 fails,his scores are:
65,0 57,0 70,0 60,0
No.2 fails,his scores are:
58,0 87,0 90,0 81,0
8.4 通过指针引用字符串
8.4.1字符串的表示形式例 8.14 定义一个字符数组,对它初始化,
然后输出该字符串
#include <stdio.h>
void main()
{ char string[]= ″I love China! ″;
printf( ″%s\n ″,string);
}
(1) 用字符数组存放一个字符串,然后输出该字符串。
(2) 用字符指针指向一个字符串可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符。
例 8.15 定义字符指针,使它指向一个字符串。
#include <stdio.h>
void main()
{ char string= ″ I love China! ″;
printf( ″%s\n ″,string);
}
例 8.16 将字符串a复制为字符串b
#include <stdio.h>
void main()
{ char a [ ]= ″I am a boy,″,b [20];
int i;
for(i=0; *(a+i)!= ′\0 ′;i++)
*(b+i)= *(a+i);
*(b+i)= ′\0 ′;
printf( ″string a is,%s\n ″,a);
printf( ″string b is,″);
for(i=0;b[i]!= ′\0 ′;i++)
printf( ″%c ″,b[i]);
printf( ″\n ″);
}
也可以设指针变量,用它的值的改变来指向字符串中的不同的字符。
例 8.17 用指针变量来处理例 8.16问题。
#include <stdio.h>
void main()
{ char a [ ] =″I am a boy,″,b [20],*p1,*p2;
int i;
p1=a;p2=b;
for(; *p1!= ′\0 ′; p1++,p2++)
*p2= *p1;
*p2= ′\0 ′;
printf( ″string a is:%s\n ″,a);
printf( ″string b is,″);
for(i=0;b[i]!= ′\0 ′;i++)
printf( ″%c ″,b[i]);
printf( ″\n ″);
}
程序必须保证使p1和p2同步移动
8.4.2 字符指针作函数参数例 8.18 用函数调用实现字符串的复制
#include <stdio.h>
void main()
{void copy_string(char *from,char *to);
char *a="I am a teacher.";
char b[]="You are a student.";
char *p=b;
printf("string a=%s\nstring b=%s\n",a,p);
printf("copy string a to string b:\n ");
copy_string(a,p);
printf("\nstring a=%s\nstring b=%s\n",a,b);
}
(1) 用字符数组作参数
void copy_string(char *from,char *to)
{ for(;*from!='\0';from++,to++)
{*to=*from;}
*to='\0';
}
程序运行结果如下,
string a= I am a teacher.
string b = You are a student.
copy string a to string b:
string a = I am a teacher.
stringb= I am a teacher.
1、将 copy_string函数改写为
void copy_string ( char *from,char *to)
{ while(( *to= *from)!= ′\0 ′)
{ to++; from++;}
}
对copy _string函数还可以改写得更精练一些,请分析以下几种情况:
2,copy_string函数的函数体还可改为
{
while(( *to++= *from++)!= ′\0 ′);
}
3,copy_string函数的函数体还可写成
{
while( *from!= ′\0 ′)
*to++= *from++;
*to= ′\0 ′;
}
4、由于字符可以用其ASCII码来代替所以函数体可简化为,
{while( *
*to++= *
*to= ′\0 ′
}
5、上面的 while语句还可以进一步简化为下面的 while
语句:
while( *to++= *from++);
它与下面语句等价:
while(( *to++= *from++)!= ′\0 ′);
将 *from赋给 *to,如果赋值后的 *to值等于 ′\0 ′则循环终止( ′\0 ′已赋给 *to)
6、函数体中 while语句也可以改用 for语句:
for(;( *to++= *from++)!=0;);
或
for(; *to++= *from++;);
7、也可以用字符数组名作函数形参,在函数中另定义两个指针变量 p1,p2。函数copy _string
可写为
void copy_string ( char from[ ],char to[ ])
{ char *p1,*p2;
p1= from;p2=to;
while(( *p2++= *p1++)!= ′\0 ′);
}
8.4.3 对使用字符指针变量和字符数组的讨论字符数组和字符指针变量二者之间的区别主要有以下几点:
(1) 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第 1个字符的地址),决不是将字符串放到字符指针变量中。
(2)赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。
char str[14];
str= ″I love China! ″;
而对字符指针变量,可以采用下面方法赋值:
char *a;
a= ″I love China! ″;
(3)对字符指针变量赋初值:
char *a= ″I love China! ″;等价于
char *a;
a= ″I love Chian! ″;
而对数组的初始化:
char str[14]={ ″I love China! ″} ;
不能等价于
char str[14];
str[ ]= ″I love China! ″;
(4) 定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,
给指针变量分配内存单元,在其中可以放一个字符变量的地址。
例如,char str[10];
scanf( ″%s ″,str);
(5) 指针变量的值是可以改变的,例如:
例 8.19 改变指针变量的值
#include <stdio.h>
void main()
{ char *a= ″I love China! ″;
a=a+7;
printf( ″%s ″,a);
}
(6) 若字符指针变量 p指向字符串,就可以用指针变量带下标的形式引用所指的字符串中的字符。如有:
char *a=″I love China!″;
(7) 字符数组中各元素的值是可以改变的 (可以对它们再赋值 ),但字符指针变量指向的字符串中的内容是不可以被取代的 (不能对它们再赋值 )。如:
char a[]=”House”;
char *b=” House”;
a[2]=’r’; /* 合法,r取代 u */
b[2]=’r’; /* 非法,字符串常量不能改变 */
8.5 指向函数的指针
8.5.1 什么是函数指针如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个 函数的指针 。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
8.5.2 用函数指针变量调用函数如果想调用一个函数,除了可以通过函数名调用以外,还可以通过指向函数的指针变量来调用该函数。
例 8,20 用函数求a和b中的大者。
( 1)通过函数名调用函数。
#include <stdio.h>
void main()
{ int max( int,int);
int a,b,c;
printf("please enter a and b:");
scanf( ″%d,%d ″,&a,&b);
c=max(a,b);
printf( ″a=%d,b=%d,max=%d
″,a,b,c);
}
int max( int x,int y)
{ int z;
if(x>y)z=x;
else z=y;
return(z);
}
运行情况如下:
please enter a and b:3,8↙
a=3,b=8,max=8
将 程序 改写为
#include <stdio.h>
void main()
{int max(int,int);
int (*p)(int,int); int a,b,c;
p=max; printf("please enter a anb b:");
scanf("%d,%d",&a,&b);
c=(*p)(a,b); printf("a=%d,b=%d,max=%d\n",a,b,c);}
int max(int x,int y)
{int z;
if(x>y) z=x;
else z=y;
return(z);}
(2)
例 8.21 输入两个整数,然后让用户选择 1或 2,选 1时调用 max函数,输出二者中的大数,选 1时调用 min函数,输出二者中的小数。
#include <stdio.h>
void main()
{int max(int,int);
int min(int,int); 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);
if (n==1) p=max;
else if (n==2) p=min; c=(*p)(a,b);
printf("a=%d,b=%d,",a,b);
if (n==1) printf("max=%d\n",c);
else printf("min=%d\n",c);
}
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);}
运行情况如下:
please enter a and b,4,9↙
please choose 1 or 2:1↙
a=3,b=8,max=8
再运行一次:
please enter a and b,4,9↙
please choose 1 or 2:2↙
a=3,b=8,min=3
8.5.3 用指向函数的指针作函数参数函数指针变量常用的用途之一是把指针作为参数传递到其他函数。指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
实参函数名 f1 f2
↓ ↓
void fun( int (*x1)(int),int (*x2)(int,int))
{ int a,b,i,j;
a=( *x1)(i); / *调用f1函数 */
b=( *x2)(i,j); / *调用f2函数 */
}
例 8.22 有两个整数a和b,由用户输入 1,2或 3。如输入 1,程序就给出a和b中大者,输入 2,就给出a和b
中小者,输入 3,则求a与b之和。
#include <stdio.h>
void main()
{int max(int,int); /* 函数声明 */
int min(int,int); /* 函数声明 */
int add(int,int); /* 函数声明 */
int fun(int x,int y,int (*p)(int,int)); /* 函数声明 */
int a=34,b=-21,n;
printf("please choose 1,2 or 3:");
scanf("%d",&n);
if (n==1) fun(a,b,max);
else if (n==2) fun(a,b,min);
else if (n==3) fun(a,b,add); }
int min(int x,int y)
{int z;
if(x<y)z=x;
else z=y;
printf("min=%d");
return(z);}
int add(int x,int y)
{int z;
z=x+y;
printf("sum=%d");
return(z);}
int fun(int x,int y,int (*p)(int,int))
{int resout;
resout=(*p)(x,y);
printf("%d\n",resout); }
运行情况如下:
① please choose 1,2 or 3,1↙
max= 34
② please choose 1,2 or 3,2↙
min=2 1
③ please choose 1,2 or 3,3↙
sum= 13
8.6 返回指针值的函数一个函数可以带回一个整型值、字符值、实型值等,
也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。
这种带回指针值的函数,一般定义形式为类型名 *函数名(参数表列) ;
例如:
int *a( int x,int y) ;
例 8.23 有若干个学生的成绩(每个学生有4门课程),
要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>
void 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,m;
printf( ″enter the number of student,″);
scanf( ″%d ″,&m);
printf( ″The scores of No.%d are,\n ″,m);
p= search(score,m);
for(i=0;i<4;i++=
printf( ″%5.2f\t ″,*(p+i));
}
float * search( float (*pointer )[ 4],int n)
{ float *pt;
pt= *(pointer+n);
return(pt);
}
运行情况如下:
enter the number of student,1 ↙
The scores of No,1 are:
56.00 89.00 67.00 88.00
8.7 指针数组和多重指针
8.7.1 指针数组的概念一个数组,若其元素均为指针类型数据,称为 指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。
一维指针数组的定义形式为,
例如:
*p [4 ];
例 8.24 将若干字符串按字母顺序(由小到大)输出。
#include <stdio.h>
#include <string.h>
void main()
{ void sort( char *name[ ],int n);
void printf( char *name[ ],int n);
char *name[ ] ={"Follow me","BASIC","Great
Wall″,"FORTRAN","Computer design"};
int n=5;
sort(name,n);
print(name,n);
}
void sort( char *name[ ],int n)
{ 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)
{int i;
for(i=0;i<n;i++)
printf( ″%s\n ″,name[i]);
}
运行结果为:
BASIC
Computer design
FORTRAN
Follow me
Great Wall
8.7.2 指向指针数据的指针定义一个指向指针数据的指针变量,
**p;
p的前面有两个 *号。 *运算符的结合性是从右到左,
因此 **p相当于 *( *p),显然 *p是指针变量的定义形式。如果没有最前面的 *,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个
*号,表示指针变量p是指向一个字符指针变量的。
*p就是p所指向的另一个指针变量。
例 8.25 使用指向指针数据的指针变量
#include <stdio.h>
void 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);
}
}
例 8.26 一个指针数组的元素指向整型数据的简单例子
#include <stdio.h>
void 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=num;
for(i=0;i<5;i++=
{ printf( ″%d ″,**p);
p++;
}
}
8.8 动态内存分配与指向它的指针变量
8.8.1 什么是内存的动态分配前面介绍过全局变量和局部变量,全局变量是分配在内存中的静态存储区的,非静态的局部变量
(包括形参 )是分配在内存中的动态存储区的,这个存储区是一个称为 栈 (stack)的区域。除此以外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据是临时存放在一个的特别的自由存储区,称为 堆 (heap)区。
8.8.2 怎样建立内存的动态分配对内存的动态分配是通过系统提供的库函数来实现的,
主要有以下 4个函数,
(1)
函数原型为
void *malloc(unsigned int size);
作用是在内存的动态存储区中分配一个长度为size
的连续空间。
(2)
函数原型为
void *calloc( unsigned n,unsigned size) ;
作用是在内存的动态存储区中分配n个长度为size
的连续空间。
8.8.2 怎样建立内存的动态分配
(3)
函数原型为
void free( void *p) ;
作用是释放由指针变量p指向的动态空间,使这部分空间能被其他变量使用。
(4) realloc函数函数原型为
void *realloc(void *p,unsigned int size);
如果已经通过 malloc函数或 calloc函数获得了临时分配域,
想改变函数其大小,可以用 recalloc函数重分配。
8.8.3 void指针类型
void指针类型,允许定义一个基类型为void
的指针变量,它不指向任何具体的数据。请注意:不要把“指向 void类型”理解为能指向,任何的类型,
的数据,而应理解为“指向 空类型,或,不指向确定的类型”的数据。在将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。
例如,
int a=3; /* 定义 a为整型变量 */
int *p1=&a; /* 基类型为 int型 */
void *p2; /* 基类型为 void型 */
p2=(void )p1; /* 转换为 void*类型然后赋值 */
printf(“%d”,*p1); /* 合法,输出 a的值 */
printf(“%d”,*p2); /* 错误,p2是无指向的,不能指向 a */
例 8.27 建立动态数组,输入 5个学生的成绩,另外用一个函数检查其中有无低于 60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>
void main()
{ void check(int *);
int *p1,i;
void *p2;
p2=malloc(5*sizeof(int));
p1=(int *)p2;
for(i=0;i<5;i++)
scanf("%d",p1+i);
check(p1);
free(p2);
}
void check(int *p)
{
int i;
printf("They are fail:");
for(i=0;i<5;i++)
if (p[i]<60) printf("%d ",p[i]);
printf("\n");
}
主要内容
8.1 地址和指针的概念
8.2 变量的指针和指向变量的指针变量
8.3 通过指针引用数组
8.4 通过指针字符串
8.5 指向函数的指针
8.6 返回指针值的函数
8.7 指针数组和多重指针
8.8 动态内存分配与指向它的指针变量
8.1地址和指针的概念内存区的每一个字节有一个编号,这就是
,地址,。如果在程序中定义了一个变量,在对程序进行编译时,系统就会给这个变量分配内存单元。
1、按变量地址存取变量值的方式称为,直接访问,方式
printf( ″%d ″,i);
scanf( ″%d ″,&i);
k=i+j;
2,另一种存取变量值的方式称为,间接访问,的方式。
即,将变量i的地址存放在另一个变量中。
在C语言中,指针是一种特殊的变量,它是存放地址的。
一个变量的地址称为该变量的,指针,。
例如,地址 2000是变量i的指针。如果有一个变量专门用来存放另一变量的地址(即指针),则它称为,指针变量,。上述的 i_pointer就是一个指针变量。
指针和指针变量的定义:
8.2 变量的指针和指向变量的指针变量
8.2,1 怎样定义指针变量定义指针变量的一般形式为基类型 *指针变量名;
下面都是合法的定义:
float *pointer_3;
char *pointer_4;
可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向一个该变量。
例如:
pointer_1=&i;
pointer_2=&j;
在定义指针变量时要注意两点:
(1)指针变量前面的,*”,表示该变量的类型为指针型变量。
例,float *pointer_1;
指针变量名是 pointer_1,而不是 * pointer_1 。
(2) 在定义指针变量时必须指定基类型。
需要特别注意的是,只有整型变量的地址才能放到指向整型变量的指针变量中。下面的赋值是错误的 ∶
float a;
int * pointer_1;
pointer_1=&a;
在对指针变量赋值时需要注意两点:
⑴ 指针变量中只能存放地址(指针),不要将一个整数赋给一个指针变量。
例,* pointer_1=100; /* pointer_1是指针变量,100是整数,不合法 */
(2) 赋给指针变量的变量地址不能是任意的类型,而只能是与指针变量的基类型具有相同类型的变量的地址。
在引用指针变量时,可能有三种情况:
⑴给指针变量赋值。如:
p=&a;
⑵ 引用指针变量的值。如:
printf(“%o”,p);
⑶ 引用指针变量指向的变量。
有关的两个运算符:
(1) & 取地址运算符。 &a是变量 a的地址。
(2) * 指针运算符 (或称“间接访问”运算符),*p
是指针变量 p指向的对象的值。
8.2.2 怎样引用指针变量例 8.1 通过指针变量访问整型变量
#include <stdio.h>
void main ( )
{ int a,b;
int *pointer_1,*pointer_2;
a=100;b=10;
pointer_1=&a; /*把变量a的地址赋给
pointer_1 */
pointer_2=&b; /*把变量b的地址赋给
pointer_2 */
printf( ″%d,%d \n ″,a,b);
printf( ″%d,%d \n ″,*pointer_1,*pointer_2);
}
例 8,2 输入a和b两个整数,按先大后小的顺序输出
a和b。
#include <stdio.h>
void main()
{ int *p 1,*p 2,*p,a,b;
scanf( ″%d,%d ″,&a,&b);
p 1=&a;p2=&b;
if(a<b)
{p=p1;p1=p2;p2=p;}
printf( ″a =%d,b =%d \n \n ″,a,b);
printf( ″max=%d,min=%d \n ″,*p 1,*p 2);
}
运行情况如下:
5,9 ↙
a=5,b=9
max=9,min=5
当输入a=5,b=9时,由于a<b,
将p1和p2交换。交换前的情况见图
(a),交换后见图(b)。
8.2,3 指针变量作为函数参数例 8,3 对输入的两个整数按大小顺序输出
#include <stdio.h>
void main()
{ void swap( int *p1,int *p2) ;
int a,b;
int *pointer_1,*pointer_2;
scanf( ″%d,%d ″,&a,&b);
pointer_1 =&a; pointer_2 =&b;
if(a<b) swap( pointer_1,pointer_2 );
printf( ″\n%d,%d \n ″,a,b);
}
void swap( int *p1,int *p2)
{ int temp;
temp= *p 1;
*p1= *p2;
*p2= temp;
}
例 8.4输入 3个整数a,b,c,要求按大小顺序将它们输出。
用函数实现改变这 3个变量的值。
#include <stdio.h>
void main()
{ void exchange( int *q 1,int *q 2,int *q 3) ;
int a,b,c,*p1,*p2,*p3;
scanf( ″%d,%d,%d ″,&a,&b,&c);
p1=&a;p2=&b;p3=&c;
exchange (p1,p2,p3);
printf( ″\n%d,%d,%d\n ″,a,b,c);
}
void exchange( int *q1,int *q2,int *q3)
{ void swap( int *pt1,int *pt2) ;
if( *q1< *q2) swap(q1,q2);
if( *q1< *q3) swap(q1,q3);
if( *q2< *q3= swap(q2,q3);
}
void swap( int *pt1,int *pt2)
{ int temp;
temp= *pt1;
*pt1= *pt2;
*pt2= temp;
}
8.3 通过指针引用数组一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组元素(把某一元素的地址放到一个指针变量中)。所谓 数组元素的指针就是数组元素的地址 。
8.3.1 数组元素的指针可以 用一个指针变量指向一个数组元素。
例如,int a[10]; (定义a为包含10个整型数据的数组 )
int *p; (定义p为指向整型变量的指针变量 )
p=& a[0]; (把a[0]元素的地址赋给指针变量p )
也就是使p指向a数组的第0号元素 。
C语言规定在 指针指向数组元素时,可以对指针进行以下运算:
加一个整数 (用 +或 +=),如 p+1
减一个整数 (用 -或 -=),如 p-1
自加运算,如 p++,++p
自减运算,如 p--,--p
两个指针相减,如 p1-p2 (只有 p1和 p2都指向同一数组中的元素时才有意义 )。
8.3.2 指针的运算分别说明如下:
(1)如果指针变量 p已指向数组中的一个元素,则p+1
指向同一数组中的下一个元素,p -1指向同一数组中的上一个元素。
(2) 如果 p原来指向 a[0],执行 ++p后 p的值改变了,在 p的原值基础上加 d,这样 p就指向数组的下一个元素 a[1]。
(3) 如果p的初值为&a [0 ],则p+i和a+i就是数组元素a[i]的地址,或者说,它们指向a数组的第i个元素 。
(4) *(p+i)或 *(a+i)是p+i或a+i所指向的数组元素,即a[i]。
(5) 如果指针变量 p1和 p2都指向同一数组,如执行 p2-p1,
结果是两个地址之差除以数组元素的长度。
8.3,3 通过指针引用数组元素引用一个数组元素,可以用:
(1) 下标法,如a[i]形式;
(2) 指针法,如 *(a+i)或 *(p+i) 。
其中a是数组名,p是指向数组元素的指针变量,其初值p=a。
例 8.5 输出数组中的全部元素假设有一个a数组,整型,有10个元素。要输出各元素的值有三种方法:
(1)下标法
#include <stdio.h>
void main()
{ int a[10];
int i;
for(i=0;i<10;i++)
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
printf( ″%d ″,a[i]);
}
(2) 通过数组名计算数组元素地址,找出元素的值。
#include <stdio.h>
void main()
{ int a[10];
int i;
for(i=0;i<10;i++ )
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(i=0;i<10;i++)
printf( ″%d ″,*(a+i));
}
(3) 用指针变量指向数组元素。
#include <stdio.h>
void main()
{ int a[10];
int *p,i;
for(i=0;i<10;i++)
scanf( ″%d ″,&a[i]);
printf( ″\n ″);
for(p=a;p<(a+10);p++)
printf( ″%d ″,*p);
}
例 8.6 通过指针变量输出a数组的10个元素。
#include <stdio.h>
void main()
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++ )
scanf( ″%d ″,p++);
printf( ″\n ″);
for(i=0;i<10;i++,p++ )
printf( ″%d ″,*p);
}
程序运行情况:
1 2 3 4 5 6 7 8 9 0↙
22153 234 0 0 30036 25202 11631 8259 8237 28483
显然输出的数值并不是a数组中各元素的值
#include <stdio.h>
void main()
{ int *p,i,a[10];
p=a;
for(i=0;i<10;i++)
scanf( ″%d ″,p++);
printf( ″\n ″);
p=a;
for(i=0;i<10;i++,p++ )
printf( ″%d ″,*p);
} 程序运行情况:1 2 3 4 5 6 7 8 9 0↙
1 2 3 4 5 6 7 8 9 0
8.3.4 用数组名作函数参数在第 7章中介绍过可以用数组名作函数的参数。
如,void main()
{if( int arr[],int n) ;
int array[10];
┇
f( array,10);
┇
}
void f (int arr[ ],int n )
{
┇
}
例 8.7 将数组a中n个整数按相反顺序存放
#include <stdio.h>
void 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 in verted,\n ″);
for(i=0;i<10;i++)
printf ( ″%d,″,a[i]);
printf ( ″\n ″);
}
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;
}
return;
}
运行情况如下:
The original array:
3,7,9,11,0,6,7,5,4,2
The array has been inverted:
2,4,5,7,6,0,11,9,7,3
#include <stdio.h>
void 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 in verted,\n ″ );
for(i=0;i<10;i++)
printf ( ″%d,″,a[i]);
printf ( ″\n ″); }
对刚才的程序可以作一些改动。将函数 inv中的形参x改成指针变量。
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--)
{t emp= *i; *i= *j; *j= temp;}
return;
}
如果有一个实参数组,想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况:
(1) 形参和实参都用数组名,如:
void main() void f ( int x [ ],int n)
{ int a[10]; {
… …
f (a,10); }
}
(2) 实参用数组名,形参用指针变量。如:
void main() void f ( int *x,int n)
{ int a[10]; {
… …
f (a,10); }
}
(3)实参形参都用指针变量。例如:
void main() void f( int *x,int n)
{ int a[10],*p=a; {
┇ ┇
f( p,10); }
}
(4) 实参为指针变量,形参为数组名。如:
void main() void f( int x[ ],int n)
{int a[10],*p=a; {
┇ ┇
f( p,10); }
}
#include <stdio.h>
void main()
{ void inv( int *x,int n) ;
int i,arr[10],*p=arr;
printf( ″The original array:\n ″);
for(i=0;i<10;i++,p++)
scanf( ″%d ″,p);
printf( ″\n ″);
p=arr;
inv(p,10); /* 实参为指针变量 */
printf( ″The array has been inverted,\n ″);
for(p=arr;p<arr+10;p++ )
printf( ″%d ″,*p);
printf( ″\n ″);
}
例 8,8 用实参指针变量改写例 8.7。
void inv( int *x,int n)
{ int p,m,temp,*i,*j;
m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++,j--)
{t emp= *i; *i= *j; *j= temp;}
return;
}
例 8,9 用选择法对10个整数按由大到小顺序排序
#include <stdio.h>
void main()
{ void sort( int x[ ],int n) ;
int *p,i,a[ 10];
p=a;
for(i=0;i<10;i++)
scanf( ″%d ″,p++);
p=a;
sort(p,10);
for(p=a,i=0;i<10;i++)
{printf( ″%d ″,*p);p++;}
}
void sort( int x [ ],int n)
{ 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;}
}
}
8.3.5 通过指针引用多维数组用指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素。但在概念上和使用上,多维数组的指针比一维数组的指针要复杂一些。
1,多维数组元素的地址先回顾一下多维数组的性质,可以认为二维数组是“数组的数组”,例,
定义 int a[ 3][ 4] ={{1,3,5,7},{9,11,
13,15},{17,19,21,23} };
则二维数组 a是由 3个一维数组所组成的。设二维数组的首行的首地址为2000,则表 示 形 式 含义 地 址
a 二维数组名,指向一维数组
a[0],即 0行首地址
2000
a[0],
*(a+0),
*a
0行 0列元素地址 2000
a+1,&a[ 1] 1行首地址 2008
a[ 1],*(a+1) 1行 0列元素 a[1][0]的地址 2008
A[1]+2,
*(a+1)+2,
&a[1][2]
1行 2列元素 a[1][2] 的地址 2012
*(a[1]+2),
*(*(a+1)+2),
a[1][2]
1行 2列元素 a[ 1][ 2]的值 元素值为 13
2,指向多维数组元素的指针变量
(1) 指向数组元素的指针变量例 8.10 用指向元素的指针变量输出二维数组元素的值
#include <stdio.h>
void main()
{ int a [3][4]={ 1,3,5,7,9,11,13,15,17,19,21,23};
int *p;
for(p=a[0];p<a[0]+12;p++)
{if((p-a[0])%4==0)
printf( ″\n ″);
printf( ″%4d ″,*p); }
}
运行结果如下:
1 3 5 7
9 11 13 15
19 21 23
(2) 用指向由m个元素组成的一维数组的指针变量例 8.11 输出二维数组任一行任一列元素的值
#include <stdio.h>
void main ( )
{ int a[3][4]={ 1,3,5,7,9,11,
13,15,17,19,21,23};
int ( *p)[4],i,j;
p=a;
scanf( ″ i=%d,j=%d ″,&i,&j);
printf( ″a[%d,%d]=%d\n ″,i,
j,*( *(p+i)+j));
}
运行情况如下:
i=1,j=2 ↙
a[1,2]=13
3,用指向数组的指针作函数参数例 8.12 有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。这个题目是很简单的。只是为了说明用指向数组的指针作函数参数而举的例子。用函数 average求总平均成绩,用函数 search
找出并输出第i个学生的成绩。
#include <sydio.h>
void 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的学生的成绩 */
}
void average( float *p,int n)
{ float *p _end;
float sum=0,aver;
p_end=p+n-1;
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));
}
程序运行结果如下:
average=82.25
The score of No.2 are:
90,00 99,00 100,00 98,00
例 8.1 3 在上题基础上,查找有一门以上课程不及格的学生,打印出他们的全部课程的成绩。
#include <stdio.h>
void main()
{ void search( float (*p)[ 4],int n); /*函数声明 */
float score[3][4]={{65,57,70,60},{58,87,
90,81},{90,99,100,98}};
search( score,3);
}
void search( float (*p)[ 4],int n)
{ 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;
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 ));
printf( ″\n ″);
}
}
}
程序运行结果如下:
No.1 fails,his scores are:
65,0 57,0 70,0 60,0
No.2 fails,his scores are:
58,0 87,0 90,0 81,0
8.4 通过指针引用字符串
8.4.1字符串的表示形式例 8.14 定义一个字符数组,对它初始化,
然后输出该字符串
#include <stdio.h>
void main()
{ char string[]= ″I love China! ″;
printf( ″%s\n ″,string);
}
(1) 用字符数组存放一个字符串,然后输出该字符串。
(2) 用字符指针指向一个字符串可以不定义字符数组,而定义一个字符指针。用字符指针指向字符串中的字符。
例 8.15 定义字符指针,使它指向一个字符串。
#include <stdio.h>
void main()
{ char string= ″ I love China! ″;
printf( ″%s\n ″,string);
}
例 8.16 将字符串a复制为字符串b
#include <stdio.h>
void main()
{ char a [ ]= ″I am a boy,″,b [20];
int i;
for(i=0; *(a+i)!= ′\0 ′;i++)
*(b+i)= *(a+i);
*(b+i)= ′\0 ′;
printf( ″string a is,%s\n ″,a);
printf( ″string b is,″);
for(i=0;b[i]!= ′\0 ′;i++)
printf( ″%c ″,b[i]);
printf( ″\n ″);
}
也可以设指针变量,用它的值的改变来指向字符串中的不同的字符。
例 8.17 用指针变量来处理例 8.16问题。
#include <stdio.h>
void main()
{ char a [ ] =″I am a boy,″,b [20],*p1,*p2;
int i;
p1=a;p2=b;
for(; *p1!= ′\0 ′; p1++,p2++)
*p2= *p1;
*p2= ′\0 ′;
printf( ″string a is:%s\n ″,a);
printf( ″string b is,″);
for(i=0;b[i]!= ′\0 ′;i++)
printf( ″%c ″,b[i]);
printf( ″\n ″);
}
程序必须保证使p1和p2同步移动
8.4.2 字符指针作函数参数例 8.18 用函数调用实现字符串的复制
#include <stdio.h>
void main()
{void copy_string(char *from,char *to);
char *a="I am a teacher.";
char b[]="You are a student.";
char *p=b;
printf("string a=%s\nstring b=%s\n",a,p);
printf("copy string a to string b:\n ");
copy_string(a,p);
printf("\nstring a=%s\nstring b=%s\n",a,b);
}
(1) 用字符数组作参数
void copy_string(char *from,char *to)
{ for(;*from!='\0';from++,to++)
{*to=*from;}
*to='\0';
}
程序运行结果如下,
string a= I am a teacher.
string b = You are a student.
copy string a to string b:
string a = I am a teacher.
stringb= I am a teacher.
1、将 copy_string函数改写为
void copy_string ( char *from,char *to)
{ while(( *to= *from)!= ′\0 ′)
{ to++; from++;}
}
对copy _string函数还可以改写得更精练一些,请分析以下几种情况:
2,copy_string函数的函数体还可改为
{
while(( *to++= *from++)!= ′\0 ′);
}
3,copy_string函数的函数体还可写成
{
while( *from!= ′\0 ′)
*to++= *from++;
*to= ′\0 ′;
}
4、由于字符可以用其ASCII码来代替所以函数体可简化为,
{while( *
*to++= *
*to= ′\0 ′
}
5、上面的 while语句还可以进一步简化为下面的 while
语句:
while( *to++= *from++);
它与下面语句等价:
while(( *to++= *from++)!= ′\0 ′);
将 *from赋给 *to,如果赋值后的 *to值等于 ′\0 ′则循环终止( ′\0 ′已赋给 *to)
6、函数体中 while语句也可以改用 for语句:
for(;( *to++= *from++)!=0;);
或
for(; *to++= *from++;);
7、也可以用字符数组名作函数形参,在函数中另定义两个指针变量 p1,p2。函数copy _string
可写为
void copy_string ( char from[ ],char to[ ])
{ char *p1,*p2;
p1= from;p2=to;
while(( *p2++= *p1++)!= ′\0 ′);
}
8.4.3 对使用字符指针变量和字符数组的讨论字符数组和字符指针变量二者之间的区别主要有以下几点:
(1) 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第 1个字符的地址),决不是将字符串放到字符指针变量中。
(2)赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。
char str[14];
str= ″I love China! ″;
而对字符指针变量,可以采用下面方法赋值:
char *a;
a= ″I love China! ″;
(3)对字符指针变量赋初值:
char *a= ″I love China! ″;等价于
char *a;
a= ″I love Chian! ″;
而对数组的初始化:
char str[14]={ ″I love China! ″} ;
不能等价于
char str[14];
str[ ]= ″I love China! ″;
(4) 定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,
给指针变量分配内存单元,在其中可以放一个字符变量的地址。
例如,char str[10];
scanf( ″%s ″,str);
(5) 指针变量的值是可以改变的,例如:
例 8.19 改变指针变量的值
#include <stdio.h>
void main()
{ char *a= ″I love China! ″;
a=a+7;
printf( ″%s ″,a);
}
(6) 若字符指针变量 p指向字符串,就可以用指针变量带下标的形式引用所指的字符串中的字符。如有:
char *a=″I love China!″;
(7) 字符数组中各元素的值是可以改变的 (可以对它们再赋值 ),但字符指针变量指向的字符串中的内容是不可以被取代的 (不能对它们再赋值 )。如:
char a[]=”House”;
char *b=” House”;
a[2]=’r’; /* 合法,r取代 u */
b[2]=’r’; /* 非法,字符串常量不能改变 */
8.5 指向函数的指针
8.5.1 什么是函数指针如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个 函数的指针 。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
8.5.2 用函数指针变量调用函数如果想调用一个函数,除了可以通过函数名调用以外,还可以通过指向函数的指针变量来调用该函数。
例 8,20 用函数求a和b中的大者。
( 1)通过函数名调用函数。
#include <stdio.h>
void main()
{ int max( int,int);
int a,b,c;
printf("please enter a and b:");
scanf( ″%d,%d ″,&a,&b);
c=max(a,b);
printf( ″a=%d,b=%d,max=%d
″,a,b,c);
}
int max( int x,int y)
{ int z;
if(x>y)z=x;
else z=y;
return(z);
}
运行情况如下:
please enter a and b:3,8↙
a=3,b=8,max=8
将 程序 改写为
#include <stdio.h>
void main()
{int max(int,int);
int (*p)(int,int); int a,b,c;
p=max; printf("please enter a anb b:");
scanf("%d,%d",&a,&b);
c=(*p)(a,b); printf("a=%d,b=%d,max=%d\n",a,b,c);}
int max(int x,int y)
{int z;
if(x>y) z=x;
else z=y;
return(z);}
(2)
例 8.21 输入两个整数,然后让用户选择 1或 2,选 1时调用 max函数,输出二者中的大数,选 1时调用 min函数,输出二者中的小数。
#include <stdio.h>
void main()
{int max(int,int);
int min(int,int); 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);
if (n==1) p=max;
else if (n==2) p=min; c=(*p)(a,b);
printf("a=%d,b=%d,",a,b);
if (n==1) printf("max=%d\n",c);
else printf("min=%d\n",c);
}
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);}
运行情况如下:
please enter a and b,4,9↙
please choose 1 or 2:1↙
a=3,b=8,max=8
再运行一次:
please enter a and b,4,9↙
please choose 1 or 2:2↙
a=3,b=8,min=3
8.5.3 用指向函数的指针作函数参数函数指针变量常用的用途之一是把指针作为参数传递到其他函数。指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
实参函数名 f1 f2
↓ ↓
void fun( int (*x1)(int),int (*x2)(int,int))
{ int a,b,i,j;
a=( *x1)(i); / *调用f1函数 */
b=( *x2)(i,j); / *调用f2函数 */
}
例 8.22 有两个整数a和b,由用户输入 1,2或 3。如输入 1,程序就给出a和b中大者,输入 2,就给出a和b
中小者,输入 3,则求a与b之和。
#include <stdio.h>
void main()
{int max(int,int); /* 函数声明 */
int min(int,int); /* 函数声明 */
int add(int,int); /* 函数声明 */
int fun(int x,int y,int (*p)(int,int)); /* 函数声明 */
int a=34,b=-21,n;
printf("please choose 1,2 or 3:");
scanf("%d",&n);
if (n==1) fun(a,b,max);
else if (n==2) fun(a,b,min);
else if (n==3) fun(a,b,add); }
int min(int x,int y)
{int z;
if(x<y)z=x;
else z=y;
printf("min=%d");
return(z);}
int add(int x,int y)
{int z;
z=x+y;
printf("sum=%d");
return(z);}
int fun(int x,int y,int (*p)(int,int))
{int resout;
resout=(*p)(x,y);
printf("%d\n",resout); }
运行情况如下:
① please choose 1,2 or 3,1↙
max= 34
② please choose 1,2 or 3,2↙
min=2 1
③ please choose 1,2 or 3,3↙
sum= 13
8.6 返回指针值的函数一个函数可以带回一个整型值、字符值、实型值等,
也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。
这种带回指针值的函数,一般定义形式为类型名 *函数名(参数表列) ;
例如:
int *a( int x,int y) ;
例 8.23 有若干个学生的成绩(每个学生有4门课程),
要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>
void 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,m;
printf( ″enter the number of student,″);
scanf( ″%d ″,&m);
printf( ″The scores of No.%d are,\n ″,m);
p= search(score,m);
for(i=0;i<4;i++=
printf( ″%5.2f\t ″,*(p+i));
}
float * search( float (*pointer )[ 4],int n)
{ float *pt;
pt= *(pointer+n);
return(pt);
}
运行情况如下:
enter the number of student,1 ↙
The scores of No,1 are:
56.00 89.00 67.00 88.00
8.7 指针数组和多重指针
8.7.1 指针数组的概念一个数组,若其元素均为指针类型数据,称为 指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。
一维指针数组的定义形式为,
例如:
*p [4 ];
例 8.24 将若干字符串按字母顺序(由小到大)输出。
#include <stdio.h>
#include <string.h>
void main()
{ void sort( char *name[ ],int n);
void printf( char *name[ ],int n);
char *name[ ] ={"Follow me","BASIC","Great
Wall″,"FORTRAN","Computer design"};
int n=5;
sort(name,n);
print(name,n);
}
void sort( char *name[ ],int n)
{ 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)
{int i;
for(i=0;i<n;i++)
printf( ″%s\n ″,name[i]);
}
运行结果为:
BASIC
Computer design
FORTRAN
Follow me
Great Wall
8.7.2 指向指针数据的指针定义一个指向指针数据的指针变量,
**p;
p的前面有两个 *号。 *运算符的结合性是从右到左,
因此 **p相当于 *( *p),显然 *p是指针变量的定义形式。如果没有最前面的 *,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个
*号,表示指针变量p是指向一个字符指针变量的。
*p就是p所指向的另一个指针变量。
例 8.25 使用指向指针数据的指针变量
#include <stdio.h>
void 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);
}
}
例 8.26 一个指针数组的元素指向整型数据的简单例子
#include <stdio.h>
void 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=num;
for(i=0;i<5;i++=
{ printf( ″%d ″,**p);
p++;
}
}
8.8 动态内存分配与指向它的指针变量
8.8.1 什么是内存的动态分配前面介绍过全局变量和局部变量,全局变量是分配在内存中的静态存储区的,非静态的局部变量
(包括形参 )是分配在内存中的动态存储区的,这个存储区是一个称为 栈 (stack)的区域。除此以外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据是临时存放在一个的特别的自由存储区,称为 堆 (heap)区。
8.8.2 怎样建立内存的动态分配对内存的动态分配是通过系统提供的库函数来实现的,
主要有以下 4个函数,
(1)
函数原型为
void *malloc(unsigned int size);
作用是在内存的动态存储区中分配一个长度为size
的连续空间。
(2)
函数原型为
void *calloc( unsigned n,unsigned size) ;
作用是在内存的动态存储区中分配n个长度为size
的连续空间。
8.8.2 怎样建立内存的动态分配
(3)
函数原型为
void free( void *p) ;
作用是释放由指针变量p指向的动态空间,使这部分空间能被其他变量使用。
(4) realloc函数函数原型为
void *realloc(void *p,unsigned int size);
如果已经通过 malloc函数或 calloc函数获得了临时分配域,
想改变函数其大小,可以用 recalloc函数重分配。
8.8.3 void指针类型
void指针类型,允许定义一个基类型为void
的指针变量,它不指向任何具体的数据。请注意:不要把“指向 void类型”理解为能指向,任何的类型,
的数据,而应理解为“指向 空类型,或,不指向确定的类型”的数据。在将它的值赋给另一指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。
例如,
int a=3; /* 定义 a为整型变量 */
int *p1=&a; /* 基类型为 int型 */
void *p2; /* 基类型为 void型 */
p2=(void )p1; /* 转换为 void*类型然后赋值 */
printf(“%d”,*p1); /* 合法,输出 a的值 */
printf(“%d”,*p2); /* 错误,p2是无指向的,不能指向 a */
例 8.27 建立动态数组,输入 5个学生的成绩,另外用一个函数检查其中有无低于 60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>
void main()
{ void check(int *);
int *p1,i;
void *p2;
p2=malloc(5*sizeof(int));
p1=(int *)p2;
for(i=0;i<5;i++)
scanf("%d",p1+i);
check(p1);
free(p2);
}
void check(int *p)
{
int i;
printf("They are fail:");
for(i=0;i<5;i++)
if (p[i]<60) printf("%d ",p[i]);
printf("\n");
}