制 作:方 斌
C语言程序设计教程郧阳师范高等专科学校计算机科学系方 斌 制作制 作:方 斌
8.1 变量的地址和指针
变量:代表“内存中的某个存储单元”
内存地址 ──内存中存储单元的编号
( 1)计算机硬件系统的内存储器中,拥有大量的存储单元(容量以1字节为单位)。为了方便管理,必须为每一个存储单元编号,这个编号就是存储单元的
“地址”。每个存储单元都有一个惟一的地址。
( 2)在地址所标识的存储单元中存放数据。
注意:内存单元的地址与内存单元中的数据是两个完全不同的概念。
制 作:方 斌
若在程序中定义了一个变量,C语言编译器就会根据定义的变量类型,为其分配一定字节数的存储单元。如定义:
int a; 分配 2个字节的内存空间
char ch; 为 ch分配 1个字节的内存空间
double x; 为 x分配 8个字节的内存空间
注意的是,C编译器为变量分配的内存空间是连续的。
制 作:方 斌
例如有定义:
float y;
C编译器就为变量 y分配 4个连续字节的内存空间。如图所示
1201 1202 1203 1204 1205 1206
y
内存内存的地址如上图所示,变量 y的地址就为它所占用的 4个字节内存单元的首地址,即变量 y的地址为 1202
制 作:方 斌
变量地址 ──系统分配给变量的内存单元的起始地址
一般情况下,程序中只需通过访问变量名来存取变量的值,这种存取方法称为“间接存取”。每个变量与具体地址的联系是由 C编译系统来完成的。
在编译系统中,对变量的存取实际上是对它所占据的存储单元进行操作,即对变量的地址进行操作,这种按变量地址进行访问变量的方法称为“直接存取”
假设有这样一个程序:
制 作:方 斌
main()
{ int num;
scanf("%d",&num);
printf("num=%d\n",num);
}
C编译程序编译到该变量定义语句时,将变量 num 登录到,符号表,中 。 符号表的关键属性有两个:一是,标识符名 ( id),,二是该标识符在内存空间中的,地址
( addr),。
id Addr
num 3000
制 作:方 斌
为描述方便,假设系统分配给变量 num的 2字节存储单元为 3000 和 3001,则起始地址 3000就是变量 num在内存中的地址 。
变量值的存取 ──通过变量在内存中的地址进行
系统执行,scanf(‖%d―,&num);‖ 和
,printf(‖num=%d\n―,num);‖时,存取变量 num值的方式可以有两种:
制 作:方 斌
3
2998 3000 3002 3004
(1)直接访问 ──直接利用变量的地址进行存取上例中 scanf(―%d‖,&num)的执行过程是这样的:
用变量名 num作为索引值,检索符号表,找到变量
num的起始地址 3000;然后将键盘输入的值 ( 假设为
3 ) 送到内存单元 3000和 3001中 。 此时,变量 num在内存中的地址和值,如图所示 。
制 作:方 斌
printf("num=%d\n",num)的执行过程,与 scanf()很相似:首先找到变量 num的起始地址 3000,然后从 3000
和 3001中取出其值,最后将它输出。
间接访问 ──通过另一变量访问该变量的值
C语言规定:在程序中可以定义一种特殊的变量 ( 称为指针变量 ),用来存放其它变量的地址 。
制 作:方 斌指针变量
如图所示:假设定义一个这样的变量 p(当然该变量也有自己在内存中的地址 2002),我们用该变量来存储另一个变量的地址,即把变量 a的内存地址 1012(当然是 a所占据存储单元的首地址)存放到变量 p中。
1012
制 作:方 斌
这种专门用来存放地址的变量就称为指针变量。
当我们用指针变量 p存储了另一个变量 a的地址后,访问变量 a可以是:先获得指针变量 p的地址 (2002),从中取出该变量的值 1012(即变量 a
的地址 ),然后再通过这个地址去访问它所代表的存储单元 (即访问变量 a)
上述情况我们称为变量 p指向变量 a,即指针变量 p中存放的是变量 a的地址。
制 作:方 斌
在 C语言中,指针被广泛使用,正因为这个原因,使得 C语言不同于其他的语言。
指针和数组、字符串、函数间数据的传递有着密不可分的联系。
指针的使用可以使得程序的代码更简洁,效率更高。某些场合,使用指针是使得运算进行的惟一途径。
指针若使用不当,概念不清,以至滥用,将降低程序的可读性,甚至导致系统崩溃。
指针变量的用途制 作:方 斌
8.2 指针变量的定义和指针变量的基类型
指针变量的定义格式为:
类型名称 *指针变量名 ;
如:
int *p1,p2; //p1,p2可以指向整型变量
char *pst; //pst可以指向字符变量
double *px; //px可以指向 double类型变量
int **p; //p定义为指向指针的指针变量制 作:方 斌指针变量的基类型
一个指针变量中存储的是一个变量在内存中所占存储单元的首地址。对整型变量而言,它代表 2个字节,对 double类型而言,它代表 8个字节。这就是基类型不同的含义。
基类型的不同将直接导致对指针移动时每次移动的字节数。例如,int *p 指向 int a,即 p的值是变量 a所占内存中两个字节中的首地址,
此时 p+1不是指向变量 a的第二个字节,而是指向 &a+2存储单元。
制 作:方 斌
8.3 指针变量的赋值
指针变量是专门用来存储地址的,所以,只要是合法的地址,都可以赋值给指针变量。
1、通过求地址运算符获得地址。例如:
int k = 1,*p,*q,*px;
double x = 100;
p = &k; //OK
q = p; //OK
px = &x; //Error基类型不匹配制 作:方 斌
2、可以给指针变量赋 0值。例如:
int *p = NULL; //OK
char *str = NULL; //OK
double *px = NULL; //OK
NULL是在 stdio.h中定义的宏,其值为 0或空 (?\0‘)
将指针赋值为 NULL表示该指针有确定的值 0。
空指针表示指针没有指向任何对象。
制 作:方 斌
注意,指针变量中只能存放地址,不能将一个非地址类型的数据(如常数等)赋给一个指针变量,如:
int *p;
p = 100; //Error
制 作:方 斌
8.4 对指针变量的操作
为表示指针变量和它指向的变量之间的关系,
用指针运算符,*” 可以间接访问相应的存储单元 。 例如,
int *p,a = 10,b;
p = &a; //OK
b = *p; //OK,该语句的含义是将 p所指存储单元 (a)的内容 (10)赋值给变量 b。 这里 *p代表它所指向的变量 a,即该语句等价于,b = a;
*p = 20; //OK,相当于赋值语句 a = 20;
制 作:方 斌
void main(viod)
{
int a,b;
int *pointer_1,*pointer_2; /* 定义指针变量 */
a = 100; b = 10;
pointer_1 = &a;
pointer_2 = &b;
printf("%d,%d\n",a,b);
printf("%d,%d\n",*pointer_1,*pointer_2);
}
指针应用实例制 作:方 斌
程序运行结果:
100,10
100,10
制 作:方 斌
1、在定义指针变量时,还未规定它指向哪一个变量,此时不能用 *运算符访问指针。只有在程序中用赋值语句具体规定后,才能用 *运算符访问所指向的变量。例:
int a;
int *p; (未规定指向哪个变量 )
*p = 100;
这种错误称为访问悬挂指针
( suspeded pointer)
int a;
int *p; (未规定指向哪个变量 )
p = &a; (规定指向 a)
*p = 100;
正确说明:
制 作:方 斌
2、区分,*运算符在不同场合的作用,编译器能够根据上下文环境判别 *的作用。
int a,b,c;
int * p; ( *表示定义指针)
p = &a;
*p = 100; ( *表示指针运算符)
c = a * b; ( *表示乘法运算符)
制 作:方 斌
3、区分 *运算符的以下用法:
int a ;
int *p = &a; /*定义指针变量时指定初值,是为 p指定初值 */
*p = 100; /*给指针 p所指向的变量赋值,这里是给变量 a赋值 */
int b = *(&a); //OK,等价于 b = a;
制 作:方 斌
4、注意指针变量本身和指针所指向的对象的区别。例如:
int *p,k = 0;
p = &k;
*p = 100; //OK,将 100存放在变量 k中,相当于 k = 100
*p = *p + 1; //OK,相当于 k = k + 1
或写成如下等价表达式:
*p += 1; //OK
++*p; //OK
(*p)++; //OK,注意不可写成 *p++;
制 作:方 斌例 8.1 用指针指向两个变量,通过指针运算输出值小的数
main()
{
int a,b,min;
int *pa,*pb,*pmin;
pa = &a; pb = &b; pmin = &min;
scanf("%d%d",pa,pb);
printf("a = %d,b = %d\n",a,b);
*pmin = *pa;
if(*pa > *pb) *pmin = *pb;
printf("min = %d\n",*pmin);
}
制 作:方 斌
void main(void)
{
int *p1,*p2,*p,a,b;
scanf("%d,%d",&a,&b);
p1 = &a; p2 = &b;
if (a < b)
{ p = p1; p1 = p2; p2 = p; }
printf("a=%d,b=%d\n",a,b);
printf("max=%d,min=%d\n",*p1,*p2);
}
[例 ] 输入 a和 b两个整数,按先大后小的顺序输出
a和 b。
制 作:方 斌程序分析:
p1 = &a; p2 = &b;
if (a<b) {p=p1; p1=p2; p2=p;}
如果 a<b则交换 p1和 p2的值。
该例不交换变量 a,b的值,而是交换指针
p1,p2的值,即交换地址。
p1指向变量 a,p2指向变量 b
即 p1的值是变量 a的地址,p2
的值是变量 b的地址
printf("max=%d,min=%d\n",*p1,*p2);
/*输出 p1和 p2所指向变量的值 */
运行结果:假设输入 3 8
则输出为,a=3,b= 8
max=8,min=3
制 作:方 斌
8.4.2 指针的移动
指针变量可以像其他变量一样进行加减运算。
对指针变量加上或减去一个整数,其实质是移动指针的指向,使其指向相邻的存储单元。
制 作:方 斌
int a0,a1,a2,a3,a4,*p = &a0,*q; //连续进行如下运算
q = p + 2; //q?a2 or q = &a2
q++; //q自增,?a3
q++; // q自增,?a4
q--; // q自减,?a3
p++; // p自增,?a1 问,p – q =?
制 作:方 斌
对指针变量加 1或减 1,此时的数字 1不再代表
10进制数 1,而是指 1个基类的存储单元。这 1
个存储单元到底是多少个字节要看基类而定。
如果基类是 int,则为 2个字节,如果是 char,
则为 1个字节,如果是 double,则为 8个字节。
指针变量加 1表示向高地址移动,减 1表示向低地址移动。
制 作:方 斌
8.4.3 指针比较
指针也要中进行关系运算。例如,p,q是指针变量,则:
p < q; //实际上比较的是 p和 q值 (地址 )的高低
p == NULL; 或 p == 0; 或 p ==?\0‘;
//判断 p是否有指向制 作:方 斌
8.5 指针作为函数的参数
当定义的函数的形式参数要求是一个指针时,
则传递给形式参数的实参必须是一个地址或指针,此时是将地址值传递给形参,这样,形参和实参都指向同一个对象(因为它们的值 —地址是相同的)。
制 作:方 斌例 8.2
int myadd(int *a,int *b) //注意这里的 *表示形参是指针
{
int sum;
sum = *a + *b; //注意这里的 *是间接求值运算符
return sum;
}
void main()
{
int x = 10,y = 20,z;
z = myadd(&x,&y); //调用函数时传递给形参的是地址
printf("z = %d\n",z);
}
制 作:方 斌
sum = *a + *b;
制 作:方 斌
8.5.2 通过传递地址可在函数中改变实参的值
前面的函数内容中,在参数传递后,形参和实参是互不相关的存储单元,在函数中改变形参的值是不会影响到实参的。但如果形参是指针,
参数传递时要将实参的地址传递给形参,虽然也是值传递,但因为传递的是地址的值,所以形参和实参将指向同一个对象,因此,在函数中改变形参所指向对象的值,也将影响到实参所指向对象的值。
制 作:方 斌例 8.3 通过函数交换变量的值
void swap(int*,int*);
void main()
{
int x = 10,y = 20;
printf("(1)x = %d,y = %d\n",x,y);
swap(&x,&y);
printf("(4)x = %d,y = %d\n",x,y);
}
制 作:方 斌
void swap(int *a,int *b)
{
int tmp;
printf("(2)a = %d,b = %d\n",*a,*b);
tmp = *a; *a = *b; *b = tmp;
printf("(3)a = %d,b = %d\n",*a,*b);
}
制 作:方 斌制 作:方 斌
8.5.3 用函数返回地址
函数的返回值也可以是一个指针 (地址 )
返回指针的函数定义:
类型 *函数名 (形参表 )
{
函数体语句 ;
}
如,int *fun(int*,int);
制 作:方 斌例 8.5 返回指针的函数
int* fun(int*,int*);
void main()
{
int *p,i,j;
i = 10; j = 20;
p = fun(&i,&j);
printf("i = %d,j = %d,*p = %d\n",i,j,*p);
}
int* fun(int* a,int* b)
{
if(*a > *b) return a;
else return b;
}
制 作:方 斌指针使用的几个细节
1、使用指针变量,应注意避免指针悬挂。
void swap(int *p1,int *p2)
{
int *p;
*p = *p1; //Error,p没有指向任何变量即 p没有初值
*p1 = *p2;
*p2 = *p;
}
制 作:方 斌
int* fun(int* a,int* b)
{
int *p;
if(*a > *b) p = a;
else p = b;
return p; //error,p的生命周期问题
}
制 作:方 斌
*p++,相当于 *(p++)。因为,*和 ++同优先级,
而 ++是右结合运算符。
*(p++)与 *(++p)的作用不同。
*(p++):先取 *p,再使 p加 1。
*(++p):先使 p加 1,再取 *p。
(*p)++表示,p指向的元素值加 1。
制 作:方 斌本章作业习题中所有的题目