c语言第十章指针

2024-04-22

c语言第十章指针(精选6篇)

篇1:c语言第十章指针

第十章 指 针

课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§1-§2

1、了解指针与地址的概念

2、掌握指针变量的定义、初始化及指针的运算 指针变量的定义、初始化及指针的运算

指针的运算

步骤一 复习引导

指针是C语言的一个重要概念,也是C语言的一个重要特色。正确而灵活地运用它,可以有效地表示复杂的数据结构;能动态分配内存;能方便地使用字符串;有效而方便地使用数组;在调用函数时能得到多于1个的值;能直接处理内存地址等,这对设计系统软件是很必要的。

指针的概念比较复杂,使用也比较灵活,因此初学者时常会出错,务请在学习本章内容时十分小心。

步骤二 讲授新课

§10.1 地址和指针的概念

计算机的主存储器被分成一个个存储单元,为了区分各存储单元,要为每个存储单元编号,这个编号即地址。

例:i =3;

或 scanf(“%d”, &i);

是将3送给 i所在的空间。

例:将3送到变量I_pointer所“指向”的单元(即I所标志的单元)。

所谓“指向”,是通过地址来体现的,I_pointer中的值为2000,它是变量I 的地址,这样就在I_pointer和变量之间建立起一种联系,即通过I_pointer能知道I的地址,从而找到变量I的内存单元。因而在C语言中,将地址形象化地称为“指针”。

意思是通过它能找到以它为地址的内存单元。一个变量的地址称为该变量的“指针”。

内存单元的地址和内存单元的内容是两个不同的概念。

指针:就是地址,即内存单元的编号。

指针变量:用来存放另一变量的地址(即指针)的变量。

如:地址2000是变量 i的指针;i_pointer是指针变量,其值就是指针2000。

§10.2变量的指针和指向变量的指针变量

变量的指针就是变量的地址。

存放变量地址的变量是指针变量,用来指向另一个变量。

*i_pointer 表示 i_pointer 所指向的变量。

一、定义一个指针变量

指针变量的定义包含三个方面的内容:

⑴ 指针类型说明,即定义变量为一个指针变量 ⑵ 指针变量名

⑶ 变量值(指针)所指向的变量的数据类型。格式:

存储类型

基类型

*指针变量名;例:int *pointer_1, *pointer_2;

float *pointer_3;

char *pointer_4;

二、指针的引用

指针变量有两个运算符: & :取地址运算符

功能:取变量地址;单目,右结合。

* :取内容运算符(“间接访问”运算符)

功能:只能跟地址,取变量所指向单元的内容;单目,右结合。

例:&a为变量a的地址,*p 为指针变量p所指向的存储单元。

例:int a=5, *p=&a;

printf(“%d”, *p);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,%dn”,a,b);

printf(“%d,%dn”,*pointer_1, *pointer_2);} 输出结果:100, 10

100, 10

评注:

1、在第3行虽然定义了两个指针变量,只是提供了两个指针变量,但并未指向任何一个整型变量。称为指针“悬空”。

2、最后一行的*pointer_1和pointer_2就是变量a和b。

3、程序中两处出现*pointer_1等,含义不同。程序第3行中的*pointer_1表示定义指针变量pointer_1。它前面的*只是表示该变量是指针变量。程序最后一行中的*pointer_1则代表变量,即pointer_1所指向的变量。

4、第5行中的pointer_1=&a 是将a的地址赋给指针变量pointer_1,而不是*pointer_1。

注意:不应写成:*pointer_1=&a;

5、从上例中可看出,*pointer_1等价于a,*pointer_2等价于b,故凡在程序中出现a的地方均可用 *pointer_1 代替。所以,若有: int x, y, *px=&x;则下面的运算均是正确的: y=*px+1;

/*把 x 的内容加1 送变量y*/ printf(“%dn”, px);

/*打印当前x 的内容*/ d=sqrt((double)px);

/*把x的平方根送变量d*/ px=0;

/*把 x 置为0*/ *px+=1;

/*把 x 的值加 1*/(*px)++;

/*使 x 的值加 1*/ y=(*px)++;

/*即y=x, x++*/

6、假设px和py都被定义为指向int 对象的指针,而且px当前已指向了int 型变量x,则执行:

py=px;

/*把指针px的内容拷贝到指针py中去*/ 即

py和px 这两个不同的指针指向了同一对象x

7、指针不是整数,不能将它与整数混为一谈。例:

# include main(){ int x=100,*p;p=x;printf(“%d”, p);}

例如:

# include main(){ int a, b, *d=&a;

b = d;

printf(“%d n”, b);

……

}——编译不出错,但得不到希望的值

关于&和*运算符的说明:

假设已执行

pointer_1=&a;

1、&*pointer_1含义是什么?

&*pointer_1与&a相同,即变量a的地址。

2、*&a的含义是什么?

先进行&a运算,得a的地址,再进行*运算。

*&a、*pointer_1及变量a等价。

3、(*pointer_1)+ + 相当于a + +。

它与*pointer_1 + + 不同。

4、*pointer_1 + + 等价于*(pointer_1 + +),即先进行*运算,得到a的值,然后使pointer_1的值改变,这样pointer_1不再指向a了。

例10.2:输入a和b两个整数,按先大后小的顺序输出a和b。

main(){ int *p1,*p2,*p, a, b;

scanf(“%d,%d”,&a,&b);

p1=&a;p2=&b;

if(a

{p=p1;p1=p2;p2=p;}

printf(“n a=%d,b=%dn”,a,b);

printf(“max=%d,min=%dn”,*p1,*p2);

} 运行情况: 5,9 a=5,b=9 max=9,min=5

三、指针变量作为函数参数

例10.3对输入的两个整数按大小顺序输出。先考察如下程序,看是否能得到预期的结果

swap(int p1, int p2){ int temp;temp = p1;p1 = p2;p2 =temp;} main(){ int a, b;

scanf(“%d, %d”, &a, &b);

if(a

printf(“n%d,%dn”,a,b);} 不能得到预期的结果。改为:

swap(int *p1,int *p2){ int temp;

temp = *p1;

*p1 = *p2;

*p2 =temp;} main(){ int a,b;

int *pointer_1,*pointer_2;

scanf(“%d,%d”,&a,&b);

pointer_1=&a;

pointer_2=&b;

if(a

swap(pointer_1,pointer_2);

printf(“n%d,%dn”,a,b);}

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

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

2、然后将指针变量作实参,将这n 个变量的地址传给所调用的函数的形参;

3、通过形参指针变量,改变该n个变量的值;

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

四、指针(变量)的初始化 指针置初始化值的格式:

存储类型

基类型

*指针名=初始化值; 如:main()

{ static int a;

int *p=&a, *p1=p;

…… } 再如: int *p = 0;

int *p = NULL;

五、指针的运算

1、指针的算术运算

指针仅能进行加、减算术运算

如:p+n , p-n , p++ , p--, ++p ,--p

p-= n , p+= n , p1-p2 等 其中n是整数,p、p1、p2均为指针;

施行加法运算时,指针向地址增大的方向移动; 施行减法运算时,指针向地址减小的方向移动; 移动长度取决于指针的基类型,由计算机决定; 如有:int a,b,c, *pt =&a;

pt++ 则指针向后移动两个字节; 再如:main()

{ int *p1,a=8,b=3;

p1=&a;

printf(“%d,%dn”,(*p1)++, *p1++);

printf(“%d,%dn”,a, *p1);

} 运行结果:3,8,4 注:p1+k = p1+k*sizeof(p1的基类型);

p1-k = p1-k*sizeof(p1的基类型);如:

strlen(char *s)

{ char *p=s;

while(*p!=„‟)p++;

return(p-s);

}

2、指针的关系运算

设指针p1,p2指向同一数组中的元素,则

p1

p1

p1= =p2:

表示p1、p2指向数组中的同一元素;

同理可推出>、>=、<=、!=比较的意义; 不可将指针与其他类型的对象作比较;

若两指针指向不同数组中的元素,也不可比较;

允许将指针与NULL或数值0进行= =或!=的比较,以便判定一个指针是否为空指针。

步骤三 课堂小结

本课介绍了指针与地址的概念,指针变量的定义、初始化及指针的运算。

指针:就是地址,即内存单元的编号。

指针变量:用来存放另一变量的地址(即指针)的变量。

例如:int a=5, *p=&a;

printf(“%d”, *p);

注意:运算符*和&的用法,指针变量的自加自减运算。步骤四 布置作业

课后作业:第十章课后练习10.1 10.2 课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§3 掌握指针与数组的知识 指向数组的指针变量

指向二维数组的指针

步骤一 复习引导

上节课介绍了指针变量的定义及其赋值。一个变量有地址,一个数组包含若干元素,每个数组元素都在内存中占用存储单元,它们都有相应的地址。指针变量既然可以指向变量,当然也可以指向数组和数组元素(把数组起始地址或某一元素的地址放到一个指针变量中)。

步骤二 讲授新课

所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。

引用数组元素可以用下标法(如a[3]),也可以用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使目标程序质量高(占内存少,运行速度快)。

一、指向一维数组的指针

定义形式:

int a[10];

int *p;

p=&a[0];

p=a;

含义:把数组的首地址赋给指针变量p。

也即: int *p=&a[0];

int *p=a;

二、通过指针引用数组元素

按C的规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素(而不是简单地加1)。

如果p的初值为&a[0],则:

p+i  a+i  &a[i],即指向a数组的第i个元素。

*(p+i) *(a+i)

a[i]。

指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价 引用数组元素时,可以用:

1、下标法,如:a[i]

2、指针法,如:*(a+i)

*(p+i)

其中,a是数组名,p是指向数组的指针

例10.5:输出数组中的全部元素

假设有一个a数组,整型,有10个元素。用三种方法输出各元素的值:

1、下标法: main(){ int a[10], i;

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

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

printf(“n”);

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

printf(“%d”,a[i]);}

2、利用数组名计算数组元素地址,找出元素的值。main(){ int a[10], i;

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

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

printf(“n”);

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

printf(“%d”,*(a+i));}

3、用指针变量指向数组元素。main(){ int a[10], *p, i;

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

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

printf(“n”);

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

printf(“%d”,*p);} 评注:

1、第1和2种方法执行效率是相同的。

2、第3种方法效率高。

3、用下标法比较直观。

在使用指针变量时,有几个问题要注意:

1、指针变量可以实现使本身的值改变。如:for(p=a;p<(a+10);p++)

2、要注意指针变量的当前值。

如:要通过指针变量输出a数组的10个元素。main(){ int a[10], i , *p=a;

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

scanf(“%d”, p++);

printf(“n”);

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

printf(“%d”, *p);} 这个程序输出的并不是a数组中各元素的值。因为第一个 for 循环结束时,p已经指向数组的末尾。再继续下去,p指向的是a数组下面的10个元素,是不可预料的。可加一句为:p=a;

3、用指针变量p指向数组元素,可以指到数组以后的内存单元。C编译程序不作下标越界检查。

4、注意指针变量的运算。如果p指向数组a,⑴ p++(或 p+=1),使p指向下一元素a[1]。⑵ *p++

等价 *(p++)。作用是先得到p指向的变量的值(即*p),然后再使p+1→p。⑶ *(p++)与*(++p)不同。前者为a[0],后者为a[1] ⑷(*p)++表示p指向的元素值加1,即(a[0])++ ⑸ 如果p当前指向a数组中第i个元素,则:

*(p--)相当于a[i--],先对p进行*运算,再使p自减; *(+ + p)相当于a[+ +i],先使p自加,再作*运算。*(-){ temp=*i;*i = *j;*j =temp;} return;} main(){ int i, a[10],*p=a;printf(“The original array: n ”);for(i=0;i<10;i++,p++)scanf(“%d”, p);printf(“n”);p=a;inv(p,10);printf(“The array is : n”);for(p=a;p

例10.10:用选择法对10个整数排序。main(){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++;} } sort(int x[],int n){ int i, j, k, t;for(i=0;ix[k])k=j;if(k!=i){ t = x[i];x[i] = x[k];x[k] = t;} }

四、指向二维数组的指针和指针变量

1、二维数组的地址如:

int a[3][4] A

a[0]

a[0] [0] a[0] [1] a[0] [2] a[0] [3]

A+1

a[1]

a[1] [0] a[1] [1] a[1] [2] a[1] [3]

A+2

a[2]

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

设首地址为2000。

 a[k]+p(0<=k< i , 0<=p

 a+k与&a[k][0]等价,而a[0]+k与&a[0][k]等价;  *(a+i)可理解为a[i],故有如下等价关系:

a+0

等价于 a[0]、a[0]+0、&a[0][0]、*(a+0)

a+1

等价于 a[1]、a[1]+0、&a[1][0]、*(a+1)

a+(i-1)等价于 a[i-1]、a[i-1]+0、&a[i-1][0]、*(a+i-1)

 *(a+k)与*a[k]是不同的,前者相当于a[k],是一个地址值;后者相当于*(a[k]+0)或*&a[k][0],是数组元素a[k][0]中存储的值。 数组元素a[k][p]就是*(a[k]+p),即*(*(a+k)+p)

步骤三 课堂小结

本课介绍了指向数组的指针,主要是指向一维数组的指针。用指针变量p指向数组a,指针变量p可以++、--,表示指向数组的上一元素或下一元素。但C编译程序不作下标越界检查。使用指针既方便有灵活,但初学者容易搞错。

步骤四 布置作业

课后作业:第十章课后练习10.31 10.5 课题:

教学目的: 教学重点: 教学难点: 第十章 指针

§3-§4 在掌握指针与数组的基础上,掌握字符串的指针与指向字符串的指针变量 指向字符串的指针变量

用指针处理字符串

步骤一 复习引导

上节课介绍了指向一维数组的指针及二维数组的地址,指向一维数组的指针也即指向元素的指针,那么指向二维数组的指针是怎样的呢?它有两种类型。

步骤二 讲授新课

2、指向二维数组的指针变量

——(1)指向数组元素的指针变量

设指针变量p=a[0]

(&a[0][0]、a、a+0、*(a+0))计算a[i][j]在n*m数组中的位置:

例:……

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

{for(j=0;j<4;j++)printf(“%4d”, *(p+i*m+j));

printf(“n”);}

……

例:用指针变量输出数组元素的值。main(){ int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

int *p;

for(p=a[0];p

{if((p-a[0])%4 == 0)printf(“n”);

printf(“%4d”, *p);

} } 计算a[i][j]在n*m数组中的位置:a[0]+i*m+j

——(2)行指针(指向由m个元素组成的一维数组的指针变量)

定义:指针基类型

(*指针名)[m]

其中,m表示二维数组每行有m列;

如: int(*p)[3];

于是,p

指向第0行元素的起始存储地址;

p+1

指向第1行元素的起始存储地址;……

p+I

指向第i 行元素的起始存储地址;

而每一行的元素的表示形式为:

第0行元素的表示为(*p)[j];

第1行元素的表示为(*(p+1))[j];……

第i 行元素的表示为(*(p+ I))[j];

例 :

#include main(){ int(*p)[4], j;

int a[4][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};for(p=a;p

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

if(j==3)printf(“%4dn”,(*p)[j]);

else printf(“%4d” ,(*p)[j]);

} 改写为:

#include main(){ int(*p)[4], i, j;

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

p=a;

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

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

printf(j==3?“%4dn”: “%4d” , *(*(p+i)+j));

}

3、多维数组的指针作函数参数

一维数组的地址可以作为函数参数传递,多维数组的地址也可作函数参数传递。在用指针变量作形参以接受实参数组名传递来的地址时,有两种方法:

1、用指向变量的指针变量;

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

例;有一个班,3个学生,各学4门课,计算总平均分数,以及第n个学生的成绩。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.2fn”,aver);} void search(float(*p)[4], int n){ int i;printf(“the score of No %d are:n”,n);for(i=0;i<4;i++)printf(“%5.2f”, *(*(p+n)+i));}

例10.15 在上题基础上,查找一门以上课程不及格的学生,打印出他们的全部课程的成绩。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

§10.4字符串的指针和指向字符串的指针变量

一、字符串的表示形式

1、用字符数组存放一个字符串。如:main()

{ char string[ ]=“I love China!”;

printf(“%sn”, string);}

2、用字符指针指向一个字符串。

如:main()

{ char *string=“I love China!”;

printf(“%sn”, string);} 例10.18 将字符串a复制为字符串b。main(){ char a[]=“I am a boy.” , b[20];

int i;

for(i=0;*(a+i)!=„‟;i++)*(b+i)= *(a+i);

*(b+i)= „‟;

printf(“string a is : %sn”, a);

printf(“string b is :”);

for(i=0;b[i]!=„‟;i++)

printf(“%c”, b[i]);

printf(“n”);}

例10.19 用指针变量来处理上例。main(){ char a[ ]=“I am a boy.” , b[20], *p1, *p2;

int i;

p1= a;p2= b;

for(;*p1!=„‟;p1++,p2++)*p2 = *p1;

*p2 = „‟;

printf(“string a is : %sn”, a);

printf(“string b is :”);

for(i=0;b[i]!=„‟;i++)

printf(“%c”, b[i]);

printf(“n”);}

二、字符串指针作函数参数

例10.20:用函数调用实现字符串的复制。——(1)用字符数组作参数。

void copy_string(char from[],char to[]){ int i=0;

while(from[i]!=„‟)

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

to[i]=„‟;

} main(){ char a[]=“I am a teach.”;

char b[]=“you are a student.”;

printf(“string a= %snstring b=%sn”,a,b);

copy_string(a,b);

printf(“nstring a=%snstring b=%sn”,a,b);} ——(2)形参用字符指针变量 void copy_string(char *from,char *to){ for(;* from!=„‟;from++, to++)

*to = *from;

*to=„‟;

} ——(3)对copy_string函数还可以简化。① void copy_string(char *from, char *to)

{ while((*to =* from)!=„‟)

{ to ++;from++;}

} ② void copy_string(char *from, char *to)

{ while((*to ++ =* from++)!=„‟);}

③void copy_string(char *from, char *to)

{ while(* from!=„‟)

*to++ = *from ++;

*to = „‟;

} ④void copy_string(char *from, char *to)

{ while(*to++ = *from ++);}

⑤void copy_string(char *from, char *to)

{ for(;*to++ = *from ++;);}

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

{ char *p1,*p2;

p1=from;p2=to;

while((*p2++ = *p1++)!=„‟);

}

三、字符指针变量和字符数组的讨论

1、字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,决不是将字符串放到字符指针变量中。

2、赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值;

char str[14];

str=“I love China.”;对字符指针变量,可以采用下面方法赋值:

char *a;

a= “I love China.”;

/*赋给a的是串的首地址*/

3、对字符指针变量赋初值:

char *a=“I love China.”;

等价于

char *a;

a=“I love China.”;

而对数组的初始化:

char str[14]={“I love China.”};不等价于

char str[14];

str[]=“I love China.”;

即数组可以在变量定义时整体赋初值,但不能在赋值语句中整体赋值。

4、如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋一个地址值,则它并未具体指向一个确定的字符数据。这很危险。

如: char str[10];

scanf(“%s”,str);是可以的 char *a;

scanf(“%s”,a);

能运行,但危险,不提倡,在a单元中是一个不可预料的值。

应当 char *a,str[10];a=str;scanf(“%s”,a);

5、指针变量的值是可以改变的,数组名虽然代表地址,但它的值是不能改变的。可以下标形式引用所指的字符串中的字符。

如:main()

{char *a=“I love China.”;

a=a+7;

printf(“%s”,a);}

又如:char str[]={“I love China.”};str=str+7;printf(“%s”,str);是错的

6、用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。也可以用字符数组实现,但由于不能采用赋值语句对数组整体赋值。

如:char *format;format=“a=%d,b=%fn”;printf(format,a,b);

等价于:printf(“a=%d,b=%fn”,a,b);

也可以:char format[ ]=“a=%d,b=%fn”;printf(format,a,b);

步骤三 课堂小结

本课介绍了指针与二维数组、指针与字符串,指向二维数组的指针有指向元素的指针和行指针,使用时应注意它们的区别。我们既要掌握用数组处理字符串,也要掌握用指针变量处理字符串。要区分这两种方法的不同之处。

步骤四 布置作业

课后作业:第十章课后练习10.61 10.7 课题: 第十章 指针

§5-§7 教学目的: 了解指针与函数的概念 教学重点:

教学难点: 指针数组,二级指针 掌握指针数组,二级指针等知识 掌握指针数组,二级指针等知识

步骤一 复习引导

前面介绍了指针与维数组、指针与字符串,我们可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。

步骤二 讲授新课

§10.5函数的指针和指向函数的指针变量

函数的地址:函数存储区域的首地址就是该函数的入口点,其函数名表示了入口地址。

一、函数指针变量的定义:

存储类型

数据类型

(*函数指针名)();

例:static int(*p)();例10.23 求a和b中的大者。

main()

{ int max(int, int);

int a,b,c;

scanf(“%d,%d”,&a,&b);

c=max(a,b);

printf(“a=%d,b=%d,max=%d”,a,b,c);

} max(int x, int y){ int z;

if(x>y)z=x;

else z=y;

return(z);} 法2:main()

{

int max(int, int);

int(*p)();

int a,b,c;

p=max;

/*将地址送入p */

scanf(“%d,%d”,&a,&b);

c=(*p)(a,b);

/*与max(a,b)等价*/

printf(“a=%d,b=%d,max=%d”,a,b,c);

} 注:int(*p)()定义p是一个指向函数的指针变量,此函数带回整型的返回值。

说明:

1、函数的调用可以通过函数名调用,也可以通过函数指针调用。

2、(*p)()表示定义一个指向函数的指针变量,它不是固定指向哪一个函数的,而只是表示定义了这样一个类型的变量,它是专门用来存放函数的入口地址的。

3、在给函数指针变量赋值时,只需给出函数名而不必给出参数,如:p=max。

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

5、对指向函数的指针变量,像p+n、p++、p--等运算是无意义的。

二、用指向函数的指针作函数参数

函数的参数可以是变量、指向变量的指针变量、数组名、指向数组的指针变量等。现介绍指向函数的指针也可以作为参数,以便实现函数地址的传递,也就是将函数名传给形参。

它的原理可以简述如下:有一个函数(假设函数为sub),它有两个形参(x1和x2),定义x1和x2为指向函数的指针变量。在调用函数sub时,实参用两个函数名f1和f2给形参传递函数地址。这样在函数sub中就可以调用f1和f2函数了。

有人会问,既然在sub函数中要调用f1和f2函数,为什么不直接调用f1和f2而用函数指针变量呢?的确,如果只是用到f1和f2,完全可以直接在sub函数中直接f1和f2,而不必设指针变量x1、x2。但是,如果在每次调用sub函数时,要调用的函数是不固定的,这次调用f1和f2,而下次要调用f3和f4,第三次要调用的名作为实参即可,sub函数不必作任何修改。这种方法是符合结构化程序设计方法原则的,是程序设计中常使用的。

例10.24 设一个函数process,在调用它的时候,每次实现不同的功能。

main(){ int max(int ,int);

int min(int , int);

int add(int , int);

int a, b;

printf(“enter a and b:”);

scanf(“%d,%d”, &a, &b);

printf(“max=”);process(a,b,max);

printf(“min=”);process(a,b, min);

printf(“sum=”);process(a,b, add);}

max(int x, int y)

{ int z;

if(x>y)z = x;else z = y;

return(z);

}

min(int x, int y)

{ int z;

if(x

else z = y;

return(z);} add(int x, int y){ int z;

z = x+y;

return(z);}

process(int x, int y, int(*fun)(int ,int)){int result;

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

printf(“%dn” , result);}

在函数process定义中,int(*fun)(int,int)表示fun是指向函数的指针,该函数是一个整型函数,有两个整型形参。

在三次调用中,分别将函数名max、min、add作为实参将其入口地址送给process函数中的形参fun(fun是指向函数的指针变量)。例:process函数中的(*fun)(x,y)相当于max(x,y)。

注:在使用时,应将函数声明,这样编译系统将它按函数名处理(把函数入口地址作实参值),而不是作为变量名,不致出错。

Process函数无固定功能。如果max、min、add换成其它函数,此process函数不需改变,只要修改每函数的函数体。

§10.6 返回指针值的函数

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

*函数名(参数表);例: int

*a(int x, int y);

a是函数名,调用它以后能得到一个指向整型数据的指针(地址)。

例10.25 有若干个学生的成绩(每个学生有4门课程),要求在用户输入学生序号以后,能输出该学生的全部成绩。用指针函数来实现。float *search(float(*pointer)[4], int n)

{float *pt;

pt = *(pointer +n);

return(pt);

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

float *p;

int i, m;

printf(“enter the number of student:”);

scanf(“%d”,&m);

printf(“The score of No.%d are:n”, m);

p=search(score, m);

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

printf(“%5.2t”, *(p + i));

}

例10.26 对上例中的学生,找出其中有不及格课程的学生及其学生号。float *search(float(*pointer)[4]){ int i;

float *pt;

pt = *(pointer+1);

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

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

return(pt);} main(){ 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;

for(i=0;i<3;i++){ p=search(score+i);

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

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

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

printf(“%5.2t”, *(p + i));

printf(“n”);}

} }

关于函数的返回值是指针的情况,程序设计时应注意:

1、因数组名是地址常量,用于接受这种返值的对象不能是数组名,这与把数组名作为实在参数传递给形式参数的情况不同(作为形式参数的数组名总被视为指针)。

2、不应将局部于被调用函数的指针作为返值返回给调用者,理由是局部于被调用函数的数据对象执行返回语句离开被调用函数后,原来分配的被调用函数的所有局部对象的存储空间立即被收回(释放),虽然调用者已经获得了正确的地址值,但此时它指向的存储区域的内容可能已经发生了变化,或许已经分配给其他函数了。如果调用函数中仍然使用这个指针去存取那个区域中的数据,得到的可能并非原先的数据。对于这种情况的正确做法是应该把所处理的对象定义成全局对象或static型对象。

§10.7 指针数组和指向指针的指针

一、指针数组的概念

一个数组中的元素均为指针类型,称为指针数组。

形式:

存储类型

类型名

*数组名[数组长度]

例如:

static

int

*p[4]

定义指针数组时也可以进行初始化,如:

static char ch[][20]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};

char *p[ ]={ch[0],ch[1],ch[2],ch[3]};该例也可以等价定义为:

char *p[ ]={“Beijing”,“Nanjing”,“Shanghai”,“Guangzhou”};

例如: main(){ int i, min, j;

char *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};

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

{ min=i;

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

if(strcmp(p[j], p[min])<0)min=j;

temp=p[i];p[i]=p[min];p[min]=temp;

}

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

printf(“%sn”, p[i]);} 注意:不能把一个二维数组与一个指针数组混淆;

如:int a[10][10];与

int *b[10];的异同点  访问形式相同;如a[5][5],b[5][5];  占用的存储空间数不同;

 每一个b[i]必须置初值者能使用;  使用b优越

不需进行复杂的下标计算;

b[i]指向的数组并非一定要10个元素,但a中,每一行上的元素个数必须相同;因指针指向的是一个地址,故对b而言,各个b[i]指向的存储区域之间不必连续;而对a而言,必须存储100个连续的存储int型数据对象的区域。

例10.27 将若干字符串按字母顺序(由小到大)输出。main(){ void sort(char *name[], int n);

void print(char *name[], int n);

char *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};

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

{ k=i;

for(j=i+1;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

}

二、指向指针的指针

在本章开头已提到“间接访问”变量的方式。利用指针变量访问另一个变量就是“间接访问”。

如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址”。指向指针的指针用的是“二级间址”方法。从理论上讲,间址方法可以延伸到更多的级。但实际上在程序中很少有超过二级间址的。级数愈多,愈难理解,容易产生混乱,出错机会也多。

二级指针的定义:

char **p;

含义:表示指针变量p是指向一个字符指针变量(即指向字符型数据的指针变量)的。main(){ int i, min, j;

char *temp, *p[ ]={“Beging”,“Nanjing”,“Shanghai”, “Guangzhou”};

char **pp;

pp=p;

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

{ min=i;

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

if(strcmp(*(pp+j),*(pp+min))<0)min=j;

temp=*(pp+i);

*(pp+i)=*(pp+min);

*(pp+min)=temp;

}

for(i=0;i<4;i++)printf(“%sn”,*pp++);} 例10.28 使用指向指针的指针。main(){ char *name[]={“Follow me”, “Basic”, “Great Wall”, “Fortran”, “Computer”};

char **p;

int i;

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

{ p=name+i;

printf(“%sn”, *p);

}

} 例 main(){ static 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(“%dt”, **p);

p++;

}

} 运行结果: 步骤三 课堂小结

本课介绍了指针数组、二级指针、指针与函数。要搞清它们的定义及应用; 注意区分:

char a[5];

char(*a)[5];

int

*p(int x);

int(*p)();

步骤四 布置作业

《C语言习题集》同步练习课题: 第十章 指针

§7-§8 教学目的: 了解指针数组作main函数的形参 教学重点:

教学难点: 指针的应用 掌握指针的应用 掌握指针的应用

步骤一 复习引导

上节课介绍了二级指针、指针数组,而指针数组的一个重要应用是作为main函数的形参。main()函数是我们C语言程序必不可少的,以往使用时main()是不带参数的。实际上是可带参数的,如:main(argc, argv)。

步骤二 讲授新课

三、指针数组作main函数的形参

带参数的main原型:

main(int argc, char *argv[ ])

{ ……

}

说明:

第1个参数是指命令行中参数的个数,含文件名本身。

第2个参数是一个指向字符串的指针数组。

main函数是由系统调用的。当处于操作命令状态下,输入main所在的文件名(经过编译、连接后得到的可执行文件名),系统就调用main函数。参数应和命令一起给出。命令形式:

命令名

参数1

参数2

……参数n 例如:有一个目标文件名file1,今想将两个字符串“China”, “Beijing”作为传送给main函数的参数。可写成:

file1 China Beijing

例:编写一程序echo.c,实现将命令行上除程序名之外的所有给出的其他参数都回显到显示器上。

main(int argc, int *argv[ ]){ while(argc>1){ ++argv;

printf(“%s”, *argv);

--argc;} }

若将该程序编译、连接、装配成echo.exe,则在命令行上输入:

echo hello, world! 则通过虚实结合后得:argc=3,argv[0]指向echo,argv[1]指向hello,argv[2]指向world!结果为:hello, world!

§10.8有关指针的数据类型和指针运算的小结

一、有关指针的数据类型的小结

见书中的表

二、指针运算小结

1、指针变量加(减)一个整数 例:p++、p--、p+i、p-=I等

2、指针变量赋值

将一个变量地址赋给一个指针变量。p=&a;p1=p2;

3、指针变量可以有空值,即该指针变量不指向任何变量。如 :

p=NULL;

4、两个指向同一数组元素的指针变量可以相减

5、两个指向同一数组的指针变量可作关系运算

习题举例:

习题10.4

有n个整数,使其前面各数顺序向右移m个位置,最后m个数变成最前面m个数。写一函数实现以上功能,在主函数中输入n个整数,并输出调整后的n个数。

main(){ int number[20],n,m,I;

printf(“How many number?”);

scanf(“%d”,&n);

printf(“Input %d number:n”,n);

for(i=0;i

scanf(“%d”,&number[i]);

printf(“How many place you want to move?”);

scanf(“%d”,&m);

move(number, n, m);

printf(“Now,they are:n”);

for(i=0;i

printf(“%d”, number[i]);} move(int array[20], int n, int m){ int *p,array_end;

array_end = *(array+n-1);

for(p= array+n-1;p>array;p--)

*p= *(p-1);

*array=array_end;

m--;

if(m>0)move(array, n, m);}

习题10.5 有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。程序: main(){ int i, k, m, n, num[50], *p;

printf(“Input number of person:n=”);

scanf(“%d”,&n);

p=num;

for(i=0;i

*(p+i)= i+1;

i=0;k=0;m=0;

while(m

{ if(*(p+i)!=0)k++;

if(k==3)

{ *(p+i)=0;k=0;m++;}

i++;

if(i= =n)i=0;

}

while(*p==0)p++;

printf(“The last one is NO.%dn”,*p);

}

习题10.6

写出一个函数,求一个字符串的长度。在main函数中输入字符串,并输出其长度。main(){ int len;

char *str[20];

printf(“Input string:”);

scanf(“%s”, str);

len=length(str);

printf(“The length of string is %d.”,len);}

length(char *p){ int n;

n=0;

while(*p!=„‟)

{ n++;

p++;

}

return(n);} 习题10.7

有一字符串,包含n个字符。写一个函数,将此字符串中从第m个字符开始的全部字符复制成为另一个字符串。main(){ int m;

char *str1[20],*str2[20];

printf(“Input string:”);

gets(str1);

printf(“Which character that begin to copy?”);

scanf(“%d”,&m);

if(strlen(str1)

printf(“Input error!”);

else { copystr(str1,str2,m);

printf(“result:%s”,str2);

} }

copystr(char *p1, char *p2, int m){ int n;

n=0;

while(n

{n++;

p1++;

}

while(*p1!=„‟)

{ *p2 = *p1;

p1++;p2++;

}

*p2=„‟;}

习题10.9 写一个函数,将3×3的矩阵转置。main(){ int a[3][3],*p, i;

printf(“Input matrix:n”);

for(i=0;i<3;i++)scanf(“%d %d %d”, &a[i][0], &a[i][1] , &a[i][2]);

p=&a[0][0];

move(p);

printf(“Now,matrix:n”);

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

printf(“%d %d %d”,&a[i][0], &a[i][1], &a[i][2]);

} move(int *pointer){ int i, j, t;

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

for(j=i;j<3;j++)

{ t= *(pointer+ 3*i + j);

*(pointer+ 3*i +j)= *(pointer+ 3*j + i);

*(pointer+3*j + i)=t;

}

}习题10.16

输入一个字符串,内有数字和非数字字符,如:

a123x456 17960?302tab5876 将其中连续的数字作为一个整数,依次存放到一数组a中。例如123放在a[0]中,456放在a[1]中……统计共有多少个整数,并输出这些数。#include main(){ char str[50], *pstr;

int i, j, k, m, e10, digit, ndigit, a[10], *pa;

printf(“Input a string:n”);

gets(str);

printf(“n”);

pstr=&str[0];

pa=&a[0];

ndigit = 0;i = 0;j=0;

while(*(pstr+i)!=„‟)

{ if((*(pstr+i)>=„0‟)&&(*(pstr+i)<=„9‟))j++;

else { if(j>0)

{ digit = *(pstr+i-1)-48;

k=1;

while(k

{ e10=1;

for(m=1;m<=k;m++)e10 =e10*10;

digit =digit+(*(pstr+i-k)-48)*e10;

k++;

}

*pa=digit;ndigit++;

pa++;

j=0;

}

}

i++;

}

printf(“There are %d numbers in this line.They are:n”,ndigit);

j=0;

pa=&a[0];

for(j=0;j

printf(“%d”, *(pa+j));

printf(“n”);} 步骤三 课堂小结

本课主要讲了一些习题,希望通过这些例子加深对指针的了解,看到指针在程序设计中的重要应用。同学们课后需要多看、多想、多练,逐步去体会指针这一重要的概念。步骤四 布置作业

课后作业:第十章课后练习10.10、10.11

篇2:c语言第十章指针

编译时有错不要郁闷,看提示分析语法错在哪仔细辨认,一个错会引起连锁错,改错应该逐步来多改几轮。

看运行结果与设想是否矛盾,两厢不符则要把设计的逻辑询问。

篇3:浅析C语言指针

指针是C语言的一个最重要的特征, 它提供了一种统一的方法, 使其能访问远程的数据结构。但对C语言初学者而言, 在编程过程中熟练的使用指针并不能像使用int型变量一样地轻松愉快, 容易上手, 往往是不得其精髓。我们知道, 不论什么时候, 运行一个程序A, 首先都是操作系统自身的加载器把A装入内存, 然后CPU才能执行。所以A程序的所有要素都会驻留在内存的某个位置。

下面我们看一段示例程序。

首先, 编译器会为变量i和j开辟内存空间, 用来存储i和j的值。同时也会为函数cmp开辟空间来存放其代码。这样使得最终的可执行程序就变为了跟内存一一对应的序列。操作系统的加载器把这个可执行程序载入内存后, cpu就可以按一条条的语句顺序执行了。

既然内存空间同程序的所有要素是一一对应的, 那么怎么区分各要素的存放位置呢?内存使用不同的地址存放不同的要素, 如下所示。

由于变量都存放于内存地址空间, 并且与地址之间是一一对应的, 那么利用地址能做些什么呢?我们可以把地址存放到别的变量中, 以便我们可以在以后程序的某个地方使用它。C语言有一个专门用来存储内存地址的变量, 这就是指针变量, 通常我们称之为指针 (pointer) 。它是一种变量类型, 这种变量方便我们把需要操控的内存地址记忆起来。

定义指针

定义指针的运算符同乘法运算符是一样的, 都用“*”表示。定义一个指针变量在语法上是简单的, 同我们定义其他变量的区别是:首先规定它指向的变量类型, 然后并不是立即就给出其变量的标识符, 而是在变量类型同变量标识符之间插入指针运算符 (星号) , 这样就告诉编译器这是一个指针变量。

C语言中指针可以指向任何的数据类型, 包括函数。函数指针的定义是:函数返回值+ (* + 函数指针变量标识符) + (函数的参数列表) 。函数指针能构建出更加清晰的程序结构。编程中经常使用的指针定义就是这两种, 当然有些定义可能只是语法上面有意义, 但是语义上面不一定有具体的意义。例如, int* (* (* (*f) () ) []) () 声明f是一个函数指针, 该函数返回一个指针, 该指针指向数组, 该数组元素是指针, 那些指针指向返回值类型为整型指针的函数。这样的声明可能永远也不能应用到实际的代码中。

指针和数组

数组是内存中一段连续相同类型的内存数据, 这组数据的首地址以数组名字来标识。所有数组对其数据的操控都可以使用指针来实现, 同理, 指针指向一段内存数据时, 也可以使用数组下标的方式来实现操作。

数组与指针在使用上的某些地方是非常相似的, 但是数组与指针又有一些细小的区别。数组名表现为一个静态指针, 也可以直接把它赋值给指针变量, 但它的大小与指针通常是不同的。数组名的内涵在于其指代的实体是一种数据结构, 这种数据结构就是数组。数组名可以作为参数传入一个接受参数为指针的函数内部, 但是此时数组完全丢失了数组的本义, 变成了完全的指针类型, 其常量特性 (可以作自增、自减等操作) 可以被修改。并且, 数组名不能再重新赋值为其他的数组名字, 而指针变量是可以被重新赋值并指向一段新的内存地址的。

指针的运算

指针的运算指的是指针的--、++、-和+运算, 一个指针可以加上或者减去一个整数。两个指针相减得到的是指针之间相隔的元素个数。不同的指针变量之间进行相加运算尽管在语法上是合理的, 但是从语义上来讲是没有意义的。除了void型指针和函数指针以外, 所有其他类型的指针都可以进行指针运算。通过指针变量的增加或减少, 指针变量会指向新的内存地址。

一般来说, 指针变量自身的大小在理论上是指机器的字长, 但是指针变量的运算并不是按照指针变量自身的大小进行内存偏移的, 而是按照指针变量指向的变量类型大小进行内存偏移的。比如, 声明一个整形的指针p, 假定p的地址是0x4323672, 那么++p后p的值变为0x43236726。偏移的内存大小等于整形变量的内存大小4 (sizeof (int) ) 。同理, double型指针进行++运算后偏移值就是8 (sizeof (double) ) 。

指针强转

如同整形变量可以强转为浮点型变量一样, 指针类型也可以通过强转变成新的指针类型, 比如我们可以把整形指针强转为字符型指针。指针强转最诱人的地方就在于对内存数据进行操控就够了。指针强转使得指针对数据的操控更具有针对性, 而且通过指针的默认强转可以使得函数的参数更简单, 且传递的信息量是不变的。比如, void*作为参数时可以把任意的指针变量传递到函数内部进行相关的操作。

下面我们来看一个具体的例子。数据的内存布局如下图所示, 首先是一个字符型数据, 紧接着的是两个整形数据, 最后面是三个结构体A型数据。我们需要做的就是把这些数据读出来。

我们先声明一个字符型的指针p, 使其指向第一个数据的内存地址。取完第一个字符型数据后, 通过p++, 然后强转指针为整形指针, 就可以很方便地取出整形数据, 同理可取出三个结构体数据。

指针作为参数

先看一个例子, 我们有两个整形变量, x的值为777, y的值为888, 现在想构建一个函数用来交换两个整形变量的值, 使得x的值为888, y的值为777。首先我们以传值的方式构建

我们调用函数swap_value (x, y) 后, 发现x、y的值并没有被交换。造成这种结果的原因是由于函数调用时, 首先对传入的实参进行变量的拷贝, 交换的值是形参的值, 并不是实参的值。而原来的实参与拷贝后的形参变量所处的内存也不同, 所以并没有交换成功。

要想实现函数内部对这两个值的交换, 必须使得实参与拷贝后的形参变量所处的内存是相同的。我们知道了原理后, 修正函数参数列表, 以指针的方式重新构建函数如下:

这时候我们发现x、y的值被交换了。通过上面的例子可以看出, 使用指针作为参数可以修改原来的变量值, 使得函数实现的机能更加模块化, 方便了程序的设计。

野指针

前面我们已经讨论过指针变量同内存的关系, 了解了指针变量里面存放的是某个变量的内存地址, 该地址可以在程序的某个位置使用, 以方便我们更改或取得该变量的值。指针使得我们拥有了操控内存的利器, 但同时指针也是一把双刃剑。我们必须时刻确保指针变量的值是我们意图操控的内存地址。如果指针变量的值被不受控的更改或者初始化不正确, 那么我们就使用了错误的地址, 从而导致程序错误, 通常我们称这个导致程序错误的指针变量为野指针。由于使用了野指针而产生的程序错误大多时候是隐蔽的, 难于跟踪的。野指针的产生主要是由于以下几种情况。

(1) 声明了指针变量, 但是没有正确的初始化就使用了该指针变量。

(2) 使用指针变量之前没有对其进行安全检查。

(3) 指针指向的内存变为了无效值, 但没有及时对指针清零, 导致程序某处引用了该指针。

(4) 多个指针同时指向同一内存区域, 程序某处通过某个指针释放了该内存, 但是没有及时对其他的指针清零, 导致程序某处进行了错误的引用。

(5) 多线程时, 对全局的指针变量没有进行锁处理。

多级指针

定义一级指针我们使用一个‘*’, 在定义多级指针时, 是几级指针我们就使用几个‘*’。例如, 声明一个整型的二级指针 (int ** ppVar;) 。下面以这个二级指针为例说明一下二级指针的意义。

二级指针变量同样是保存了一个地址, 这个地址就是某个一级指针变量的地址, 而一级指针变量里面保存了最终需要操作的变量的地址, 如下所示。

二级指针变量的值为0x4323640, 就是一级指针变量pVar的地址, 变量pVar的值为0x4323668, 就是变量Var的地址。如果需要修改变量Var的值, 我们可以直接修正**ppVar的值就可以了。

三级指针或者更多级指针的原理与二级指针的原理是相同的, 只是需要索引的内存空间的深度增加了。在程序设计中, 引入多级指针更多的时候并不仅仅是为了关注最后一级指针所能取得的变量, 而更多的是为了使用和操控其中间的级数的内存值。比如利用二级指针作为函数的参数在某个函数内部对其分配内存, 我们更想利用的是一级指针变量自身。当然, 在进行程序设计时, 有时我们要在可读性与语法有效性之间做出选择, 在实现代码的过程中能用低级指针实现的尽量不要使用多级指针实现, 这样的代码更利于维护。

小结

在C语言中指针的使用非常的广泛, 有时指针是实现某个计算的唯一方法。同样的机能使用指针通常也可以获得更加高效、紧凑的代码。指针使得函数构建的机能更加的模块化, 使得函数参数栈更加的短小。同时在操纵字符串的运算中, 指针更加简单直观。

在大项目构建时, 把函数指针同数据封装在一起能够使得代码编程面向对象的结构, 使得后期代码的维护成本大大降低, 代码的表现也更加具有现实意义。

篇4:c语言第十章指针

关键词:存储空间图;间接寻址;一级指针;二级指针;存储单元

中图分类号:TP311文献标识码:A文章编号:1009-3044(2007)16-31093-03

A Discussion about the Relation of C Language Pointer and Assemble Language Indirect Addressing——Giving Analysis from the Storage Space Digraph

WANG Hai-yan

(Computer Science Department of Suqian College, Suqian 223800, China)

Abstract: C language is a user-oriented procedural language. Pointer is the most flexible part in this language. Assemble language is a language which is processor-oriented and there is no definite conception of pointer, but the similar conception of pointer emergences everywhere. In this paper, it present the application of pointer in assemble language through indirect addressing mode. Depending on the storage space digraph and the relation between pointer of C language and indirect addressing of assemble language, it makes it easier to understand the application and relation between the two languages.

Keywords: Storage Space Digraph; Indirect Addressing; Pointer; Second Rank Pointer; Storage Unit

1 引言

作为最基本的编程语言之一,汇编语言的重要性勿庸置疑,即使是 Linux 程序员有时也需要使用汇编语言解决实际问题,理由很简单:精简、高效和 libc 无关性。假设要移植 Linux 到某一特定的嵌入式硬件环境下,首先必然面临如何减少系统大小、提高执行效率等问题,此时或许只有汇编语言能帮上忙了。我们常说汇编语言的抽象是C语言, 而C语言中最灵活是C语言中拥有“指针”这个数据类型。那么汇编语言和C语言中的指针有什么样的关系呢?我们也知道在汇编语言中,几乎所有对内存的操作都是对给定地址的内存进行访问来完成的,那么在汇编语言中,绝大多数操作也必然和地址(即指针)产生或多或少的联系。汇编语言和CPU以及内存,端口等硬件知识是连在一起的。汇编语言中存储数据的地方不仅有寄存器而且还有存储单元,更多的数据则保存在存储单元中。因此,对编程人员而言,他肯定迫切地希望访问内存,以保存

更多的数据,本文将重点阐述访问数据的方式。

2 存储空间图的概念

画一个图形,或者观察一个图形,能帮助我们把复杂的程序设计具体化、形象化,有利于掌握程序之间的内在联系,从而更好的理解程序,激发大家对程序设计语言的兴趣。本文用形象的图形解释C语言中指针以及汇编语言中间接寻址方式之间的相互关系。文中运用类似于存储空间图的方法对两门语言进行解释。说它类似于存储空间图是因为这种图形没有用真正二进制描述空间的值和空间的地址,姑且我把这种图形叫做存储空间图。

3 两门语言之间的内在联系

我们知道在计算机中,所有的数据都是存放在存储器中的,访问数据的方式并不是唯一的。在C语言中访问单元数据有两种常见方式,一是通过名字直接访问单元数据;二是通过指针访问单元数据。直接通过单元名字访问数据,两种语言都很简单,我在这篇文章中不加以阐述。接下来我就分一级指针和二级指针分别阐述汇编语言和C语言是如何间接访问内存单元数据,又是以怎样的形式加以表现的。

3.1 C语言中的一级指针和汇编语言中一次间接寻址的联系

3.1.1 C语言中一级指针定义

指针定义形如:数据类型 *指针变量,它的存储空间图表示如下:

图1中X就是一级指针,Y是一个整形变量,X、Y的本质区别在于X单元的值是用于存放内存单元的地址,Y单元是存放任意类型的数据。X之所以指向Y,是因为Y单元的地址赋予了X,同时X所指向的类型和Y同类型。

图1

3.1.2 汇编语言中的一次间接寻址是指通过寄存器或存储单元一次间接寻找操作数。

它的存储空间图表示如下:

图2中si是cpu中的一个寄存器,si中存放着Y所在单元的地址,Y是一个单元的名称,Y单元存放着普通操作数,si之所以指向Y,是因为Y单元的地址赋予了si。

图2

图1、图2中X和si都是用于存放内存单元的地址,我们从中可以得到启迪,汇编语言中也存在着类似C语言的指针。

3.1.3 以下从编程角度深入探悉二者之间的联系。

例1 用C语言指针的方法输出a单元中的数据:

main()

{int a=3,*p;

p=&a

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

}

例2 用间接寻址方法把a单元中的数据送往al寄存器中:

data segment

a db 3

data ends

……

lea si, a

mov al, [si]

……

以上程序均是操作存储单元中的数据,但都不是通过单元名字操作的。例1用C语言中一级指针的方法,例2用汇编语言中的寄存器间接寻址的方法。那么二者有怎样的联系呢?为了讲清楚这个问题,我把这两个程序分别用以下存储空间图表示:

图3 C语言编程的存储空间图图4 汇编语言编程的存储空间图

上图中p和si其实质都是一级指针,在C语言中是用指针指向的内容,即用*p的形式来取得单元的数据,而汇编语言是通过寄存器一次间接寻址方式来取得单元内容,当然这都要求指针p和寄存器si首先获得单元的地址。(p=&a/lea si, a)。

3.2 C语言中的二级指针和汇编语言中二次间接寻址的联系

3.2.1 C语言中二级指针的定义。

指针定义形如:数据类型 **指针变量,它的存储空间图表示如下:

图5中X相对与Z这个变量是二级指针,它和一级指针的区别在于X这个指针所指向的内容仍然是某个单元的地址(图中是Z单元的地址)。要想输出Z单元的内容,则需要通过两次指向才能完成。

图5

3.2.2 汇编语言中二次间接寻址是指通过寄存器或存储单元两次间接寻找操作数。

它的存储空间图表示如下:

图6中si其实就等价于图5中定义的X,都是二级指针。但在汇编语言中si是一个指针寄存器,无论是作为一级指针还是作为二级指针,我们在使用的时候都是不需要定义的。事实上汇编语言中根本没有一级指针和二级指针的定义,也就是说汇编语言中没有C语言中定义上的繁琐,但你仍可以用C语言的思想来解释汇编语言中的指针问题。

图6

3.2.3 以下从编程角度深入探悉二者之间的联系。

例3 输出以下字符串

main()

{char *p[] = {"ab", "cd", "ef"};

char **sp = p;

int i;

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

printf("%s", **(sp+i));

}

图7 C语言编程的存储空间图

例4 累加数组array中元素和的程序段

data segment

array dw 10,20,30,40,50,60,70,80,90,100

count dw 10

sum dw?

table dw 3 dup(?);地址表

data ends

code segment

main proc far

assume cs:code, ds:data

start:push ds

……

mov table, offset ary

mov table+2, offset count

mov table+4, offset sum

mov bx, offset table

call proadd

ret

main endp

proadd procnear

……

mov si, [bx]

mov di, [bx+2]

mov cx, [di]

mov di, [bx+4]

xor ax, ax

next: add ax, [si]

add si, 2

loop next

mov [di], ax

……

proadd endp

code ends

end start

图8 汇编语言编程的存储空间图

以上程序均是通过两次操作才找出单元中的数据,前者是用C语言中二级指针二次指向的方法,后者是用汇编语言中的两次间接寻址的方法寻找操作数。从上图可知这两门语言的内在联系,即两门语言最终都是通过指针两次寻址来访问操作数。所不同的是:前者是二级指针的概念,后者是两次间接寻址。这种本质相同,只是语言描述上有所不同的概念,从存储空间图的视角可以帮助我们更好的理解他们之间的联系。

依此类推,对于多级指针的学习,我们也可以借助存储空间图来帮助我们更好的分析程序,理解程序,对于一些复杂的问题给予简化,对于不同语言之间的关系我们也能进行更好的深入思考。

4 结束语

本文通过存储空间图深入浅出的探悉了两门语言在寻找数据方面的联系。可以说C语言的指针和汇编语言的寻址方式一直困扰着很多编程者,尤其对二级指针、二次间接寻址的理解及运用更加困难。文中的例题均以最简单的题型来说明这些深奥的道理,而且从存储空间图的视角分析了二者之间的联系。相信这篇文章,不仅能帮助我们运用存储空间图解释问题、分析问题,而且能帮助我们更好的思考不同语言之间的联系,更好的去思考程序设计。

参考文献:

[1] 裘宗燕. C++程序设计语言(特别版). 北京机械工业出版社, 2002.7.

[2] Standley B.lippman. C++ Primer中文版. 人民邮电出版社,20006.3.

[3] 谭浩强. C程序设计(第二版). 清华大学出版社出版,2005.6.

[4] 钱能. C++程序设计教程. 清华大学出版社, 2005.5.

[5] 沈美明. IBM PC汇编语言程序设计. 清华大学出版社,1993.9.

[6] 钱晓捷. 汇编语言程序设计. 电子工业出版社,2003.

篇5:六:指针(C语言)

指针的基本概念 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。 可以用一个通俗的例子来说明它们之间的关系。我们到银行去存取款时, 银行工作人员将根据我们的帐号去找我们的存款单, 找到之后在存单上写入存款、取款的金额。在这里,帐号就是存单的指针, 存款数是存单的内容。对于一个内存单元来说,单元的地址即为指针, 其中存放的数据才是该单元的内容。在C语言中, 允许用一个变量来存放指针,这种变量称为指针变量。因此, 一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。图中,设有字符变量C,其内容为“K”(ASCII码为十进制数 75),C占用了011A号单元(地址用十六进数表示)。设有指针变量P,内容为011A, 这种情况我们称为P指向变量C,或说P是指向变量C的指针。 严格地说,一个指针是一个地址, 是一个常量。而一个指针变量却可以被赋予不同的指针值,是变。 但在常把指针变量简称为指针。为了避免混淆,我们中约定:“指针”是指地址, 是常量,“指针变量”是指取值为地址的变量。 定义指针的目的是为了通过指针去访问内存单元。

既然指针变量的值是一个地址, 那么这个地址不仅可以是变量的地址, 也可以是其它数据结构的地址。在一个指针变量中存放一

个数组或一个函数的首地址有何意义呢? 因为数组或函数都是连续存放的。通过访问指针变量取得了数组或函数的首地址, 也就找到了该数组或函数。这样一来, 凡是出现数组,函数的地方都可以用一个指针变量来表示, 只要该指针变量中赋予数组或函数的首地址即可。这样做, 将会使程序的概念十分清楚,程序本身也精练,高效。在C语言中, 一种数据类型或数据结构往往都占有一组连续的内存单元。 用“地址”这个概念并不能很好地描述一种数据类型或数据结构, 而“指针”虽然实际上也是一个地址,但它却是一个数据结构的首地址, 它是“指向”一个数据结构的,因而概念更为清楚,表示更为明确。 这也是引入“指针”概念的一个重要原因。

指针变量的类型说明

对指针变量的类型说明包括三个内容:

(1)指针类型说明,即定义变量为一个指针变量;

(2)指针变量名;

(3)变量值(指针)所指向的变量的数据类型,

其一般形式为: 类型说明符 *变量名;

其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

例如: int *p1;表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。

再如:

staic int *p2; /*p2是指向静态整型变量的指针变量*/

float *p3; /*p3是指向浮点变量的指针变量*/

char *p4; /*p4是指向字符变量的指针变量*/ 应该注意的是,一个指针变量只能指向同类型的变量,如P3 只能指向浮点变量,不能时而指向一个浮点变量, 时而又指向一个字符变量。

指针变量的赋值

指针变量同普通变量一样,使用之前不仅要定义说明, 而且必须赋予具体的值。未经赋值的指针变量不能使用, 否则将造成系统混乱,甚至死机。指针变量的赋值只能赋予地址, 决不能赋予任何其它数据,否则将引起错误。在C语言中, 变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。 C语言中提供了地址运算符&来表示变量的地址。其一般形式为: & 变量名; 如&a变示变量a的地址,&b表示变量b的地址。 变量本身必须预先说明。设有指向整型变量的指针变量p,如要把整型变量a 的地址赋予p可以有以下两种方式:

(1)指针变量初始化的方法 int a;

int *p=&a;

(2)赋值语句的方法 int a;

int *p;

p=&a;

不允许把一个数赋予指针变量,故下面的赋值是错误的: int *p;p=1000; 被赋值的指针变量前不能再加“*”说明符,如写为*p=&a 也是错误的

指针变量的运算

指针变量可以进行某些运算,但其运算的种类是有限的。 它只能进行赋值运算和部分算术运算及关系运算。

1.指针运算符

(1)取地址运算符&

取地址运算符&是单目运算符,其结合性为自右至左,其功能是取变量的地址。在scanf函数及前面介绍指针变量赋值中,我们已经了解并使用了&运算符。

(2)取内容运算符*

取内容运算符*是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在*运算符之后跟的变量必须是指针变量。需要注意的是指针运算符*和指针变量说明中的指针说明符* 不是一回事。在指针变量说明中,“*”是类型说明符,表示其后的变量是指针类型。而表达式中出现的“*”则是一个运算符用以表示指针变量所指的变量。

main{

int a=5,*p=&a;

篇6:学通C语言:函数型指针

int func(const int a, const int b);

那么,此时声明的函数变量add的地址即为这个函数的地址,同时,add的值保存为这个函数的地址,这个特性与数组相似:数组变量与数组变量的地址均为数组的起始地址。而在这个函数声明中,函数类型为int (const int a, const int b)。使用该函数类型来定义一个函数型指针,其方式如下:

int (* fp)(const int a, const int b);   /* 其中,参数列表的参数名a和b可省 */

上述语句将变量func定义为指向类型为int (const int a, const int b)的指针操作符和变量名两侧的小括号不可省,否则其含义大不相同。例如:

int * fp(const int a, const int b);

此时,指针操作符与数据类型int结合为int型指针类型,该语句只是声明了一个fp函数,而非定义一个函数指针。为该函数型指针赋值的方式如下:

fp = func;

被赋值的函数变量的类型必须与fp的类型完全一致,包括其返回类型和每一个形参的类型。否则程序将报错。

注意:函数型指针变量赋值时,左值与右值的类型必须完全一致。

使用函数型指针变量调用函数的方法与使用函数变量类似,得到函数地址后再带上参数列表即可。可以使用下面两种方式来调用函数:

fp(5, 6);

(*fp)(5, 6);

由于fp被赋值为函数变量func的地址,而func的值又等于其地址,所以*fp可以得到func函数的地址。因此,在调用方式上,可以粗略地将两者视为一致(实际上其后台的处理略有不同)。范例14-7演示了如何使用函数型指针来调用函数。

【范例14-7】使用函数型指针来调用函数,实现方法如示例代码14-7所示。

示例代码14-7

01   #include

02

03   int add(const int a, const int b) {         /* 定义add函数 */

04      return a + b;

05   }

06

07   int main(void) {

08      int (*fp) (const int a, const int b);      /* 定义函数指针 */

09

10      fp = add;                        /* 将其赋值为add */

11      printf(“3 + 4 = %d”, fp(3, 4));         /* 使用fp计算+ 4的值 */

12      printf(“3 + 4 = %d”, (*fp)(3, 4));      /* 使用*fp计算+ 4的值 */

13

14      printf(“%p”, add);                  /* 输出add的值 */

15      printf(“%p”, &add);                  /* 输出add的地址 */

16      printf(“%p”, fp);                  /* 输出fp的值 */

17      printf(“%p”, *fp);                  /* 输出fp指向的值 */

18

19      return 0;

20   }

【运行结果】程序运行后,

【代码解析】本程序定义了一个函数指针,并将其赋值为相应类型的函数变量add。

   第11~12行分别使用fp和*fp的方式调用函数,从图14-12的第1~2行中可以看到它们的调用结果是一样的。

   第14~17行输出了add的值和地址、fp的值和指向的值,从图14-12的第3~6行中可以看到它们的调用结果都是一样的。

上一篇:浅读《木偶奇遇记》读后感短篇心得体会精选推送下一篇:李锦记家族章程