第五章 指针与结构Word下载.docx
《第五章 指针与结构Word下载.docx》由会员分享,可在线阅读,更多相关《第五章 指针与结构Word下载.docx(31页珍藏版)》请在冰豆网上搜索。
如果采取“间接存取”方式,要存取变量i的值,就必须先找到存放“i的地址”的变量p,从中取出i的地址(2000),然后到2000、2001字节取出i的值5,见图5-2:
图5-2间接存取
由于通过地址能找到所需要的变量单元,我们可以说,地址指向该变量单元,因此,在C语言中,将地址形象化为指针。
换句话说,一个变量的地址称之为该变量的“指针”,而指针变量是在一种专门存放其他变量在内存中的地址的特殊变量。
指针变量的值是地址。
5.1.2指针变量的定义
C语言规定所有变量必须遵循先定义后使用的规则。
指针变量定义格式是:
数据类型*指针变量名;
例如:
int*p;
/*p是指向int型变量的指针变量*/
float*q;
/*q是指向float型变量的指针变量*/
char*a,b;
/*其中a是指向char型变量的指针变量,而b是字符型一般变量*/
那么,怎样使一个指针变量指向另外一个变量呢?
可以用赋值语句使一个指针变量得到另外一个变量得地址,从而使它指向该变量。
inti;
/*定义i为整型变量*/
int*p;
/*定义p为指针变量*/
p=&
/*将变量i的地址赋值给指针变量p*/
注意在定义指针变量时,要注意两点:
✧指针变量前面的“*”表示该变量的类型为指针变量。
指针变量名是p,而不是*p,这是与定义整型或者字符型变量形式不同的。
✧在定义指针变量时必须指定基类型。
并且指针变量的类型是它指向的内存单元中存放数据的类型,而不是指针变量的值的类型。
那么既然指针变量,是用来存放地址的,为什么还要指定它的类型呢?
要知道不同数据在内存中所占的字节数是不同的(例如:
整型占2个字节,字符型占1个字节),在本章的稍后将要介绍指针的移动和指针的运算,例如“使指针移动1个位置”,那么这个1代表什么呢?
如果一个指针指向的是一个整型数据,那么“使指针移动1个位置”意味着移动2个字节,如果一个指针指向的是一个字符型数据,那么“使指针移动1个位置”意味着移动1个字节。
因此必须指定指针变量的基类型,并且一个指针变量只能指向同一类型的变量。
例如以下赋值是错误的:
floata;
/*定义a为float型变量*/
/*定义p为基类型为int的指针变量*/
a;
/*错误!
将float型变量的地址存放到指向整型变量的指针变量中,*/
【例5-1】通过指针变量访问整型变量
main()
{
inta,b;
/*定义a,b为整型变量*/
int*p1,*p2;
/*定义p1,p2为指向整型变量的指针变量*/
a=100;
/*为变量a赋值为100*/
b=10;
/*为变量b赋值为10*/
p1=&
/*将a的地址赋给p1*/
p2=&
b;
/*将b的地址赋给p2*/
printf("
%d,%d\n"
a,b);
/*输出a,b的值*/
*p1,*p2);
/*输出p1,p2所指向的变量的值*/
}
运行结果为:
100,10
5.1.3指针变量的引用
1.深入理解两个运算符:
*与&
设有定义语句:
int*p,a;
✧“*”是指针运算符。
“*p”出现在定义语句和非定义语句中的含义是不一样的!
在定义语句中,星号*声明其后的变量p为一指针(地址)变量;
在非定义语句中“*p”表示指针变量p指向的地址单元内的值。
✧“&
”是地址运算符,“&
a”表示取某一普通变量a的地址。
很显然,*(&
a)与a相当。
✧p=&
表示将a的地址赋给了指针变量p,即p与&
a指向了同一地址单元!
注意
✧“p=&
”语句形式经常用到,使用时指针变量与一般变量的类型必须一致!
例如本例中p,a均为整型变量,如果p,a的类型不一致那么就是错误的!
“p=a;
”是非法的语句,=号两边变量意义不同,左边为指针变量,右边为普通变量。
✧C规定,不能直接将一个常数赋给指针变量(除0以外,代表空指针)。
【例5-2】输入a、b两个整数,使用指针变量按大小顺序输出这两个整数。
main()
{
inta,b,*p1,*p2,*p;
scanf(″%d,%d″,p1,p2);
/*等价于语句scanf(″%d,%d″,&
a,&
b);
*/
if(*p1<
*p2)/*判断a,b整数的大小*/
{
p=p1;
p1=p2;
p2=p;
printf(″a=%d,b=%d\n″,a,b);
printf(″max=%d,min=%d\n″,*p1,*p2);
}
运行情况如下:
输入:
6,8
输出:
max=8,min=6
当输入6,8后,由于*p1<
*p2,将p1和p2交换。
交换前的情况如图5-3(a),交换后
的情况如图5-3(b)。
请注意,a和b的值并没有发生交换,它们仍然保持原值,但是p1和p2的值改变了。
p1的原值为&
a,后来变成了&
b,p2的原值为&
b,后来变成了&
a。
这样在输出*p1,*p2时,实际上时输出变量b和a的值,因此输出的结果为8,6。
该问题的解决思想是目标变量值不变,改变指针变量的指向求解。
请同学们想一想:
如何实现利用指针变量直接改变目标变量的值求解?
2.指针变量的算术、增量、关系等运算
设有定义语句:
int*p,*p1,*p2,a,n,v;
intq[10];
则:
(1)指针赋值运算规定,可将一个变量的地址赋给同类型的指针变量,但不能直接将常数赋给指针变量。
如“p=&
a;
”合法,“p=5000;
”非法。
(2)指针的加减运算
✧只有当指针变量指向数组时指针的加减运算才有意义。
✧指针的加减运算是以基类型为单位(即sizeof(类型))的。
✧p+n
表示p+n*sizeof(指针类型),即从p算起,后边第n个数的地址。
✧p-n
表示p-n*sizeof(指针类型),即从p算起,前边第n个数的地址。
✧指针变量可加减一个整型表达式。
如:
p1++、p2+3、p2-2。
p++,p--,++p,--p
结果是指向下一个(或上一个)数据的地址,而不是指向下一个(或上一个)地址单元。
✧两个指针变量不能作加法运算,只有当两个指针变量指向同一数组时,进行指针变量相减才有实际意义。
当指针变量p2和p1同时指向数组q时,p2-p1结果表示两个地址之间能够存放某种类型数据的个数,当然数据类型与指针的类型须一致。
(3)当*与++、--结合时应注意其优先顺序和结合性:
三个运算符优先级相同,但结合顺序是从右向左。
v=*p++:
等价v=*(p++),先取p指向单元值赋给普通变量v,然后p自增1指向下一数据单元。
v=*++p:
表示p先自增指向下一数据单元,再将该单元之值赋给普通变量v。
v=(*p)++:
将(*p)值先赋给v,然后(*p)内容再增1。
v=++(*p):
将(*p)内容增1后赋给v。
(4)指针关系运算:
✧指向同一数组的两个指针可以进行关系运算,表明它们所指向元素的相互位置关系。
p2>
p1、p2==p1。
✧指针与一个整型数据进行比较是没有意义的。
✧不同类型指针变量之间比较是非法的。
✧零可以与任何类型指针进行==、!
=的关系运算,用于判断指针是否为空指针。
例如:
p1==0;
作用是判断指针变量p1是否为空.
5.2指针与数组
从前面知道,一个数组包含有若干元素,数组各元素都在内存中占用存储单元,它们都有相应的地址,而这些存储单元是连续的区域,数组名代表这块空间的起始地址。
而指针变量既然可以指向地址,当然也可以指向数组元素的地址。
通常人们习惯将数组的首地址存放到一个指针变量中,然后通过指针加减运算,存取数组各元素。
如定义语句
intx[]={1,2,3,4,5},*p=x;
此语句中的“*p=x”,将x数组的首地址赋给了指针变量p,显然可以在定义语句中将它替换成“*p=&
x[0]”形式。
用指针来指向数组元素,比用纯数组的方式操作数组要方便得多,因为数组名不能运算,而指针是可以运算的。
使用指针能使目标程序占内存少,运行速度快。
5.2.1指向一维数组的指针
定义一个指向数组元素的指针的方法,与以前介绍的指向变量的指针变量定义方法相同。
inta[10];
/*定义a为包含10个整型数据的数组*/
/*定义p为指向整型变量的指针变量*/
a[0];
/*把元素a[0]的地址赋给指针变量p*/
在C语言中规定数组名(不包含形参数组名,形参数组并不占有实际的内存单元)代表数组中首地址(即数组中第一个元素的地址)。
因此,下面两个语句等价:
p=a;
注意指针变量指向数组并不是指向整个数组,而是指向了数组中第一个元素。
上述“p=a;
”的作用是:
“把数组的首地址赋值给p”,而不是“把数组a各元素的值赋值给p”。
那么要通过指针引用数组元素,应该如何实现呢?
按C语言规定:
如果指针变量p已指向数组中的一个元素,那么p+1指向同一数组中的下一个元素,而不是简单的将p的值(地址)简单的加1。
例如,数组元素是float型,每个元素占4个字节,那么p+1意味着使p的值(地址)加4个字节,以使它指向下一个元素;
又例如数组元素是int型,每个元素占2个字节,那么p+1意味着使p的值(地址)加2个字节,以使它指向下一个元素。
也就是说,p+1所代表的地址实际上使p+1×
d,d代表一个数组元素所占的字节数。
根据上述,引用一个数组元素可以用两种方法:
(1)下标法,如a[0]形式;
(2)指针法,如*(a+i)或者*(p+i)。
其中a是数组名,p是指向数组元素的指针变量,初值为数组a的首地址。
例如有语句如下:
inta[10],*p;
⑴数组名是该数组的指针
✧a是数组的首地址(即a[0]的地址),是一个指针常量。
a=&
a[0],a+1=&
a[1],…,a+9=&
a[9]
✧数组元素的下标表示法:
a[0],a[1],…,a[i],…,a[9]
✧数组元素的指针表示法:
*(a+0),*(a+1),…,*(a+i),…,*(a+9)
⑵指向一维数组元素的指针变量
由于数组元素也是一个内存变量,所以此类指针变量的定义和使用与指向变量的指针变量相同。
例如有语句如下:
p=a;
/*相当于p=&
*/
此时p指向a[0],下面用p表示数组元素
✧下标表示法:
p[0],p[1],…,p[i],…,p[9]
✧指针表示法:
*(p+0),*(p+1),…,*(p+i),…,*(p+9)
提醒
用指针变量引用数组元素,必须关注其当前值。
如果指针变量p的初始值不一样,那么用p表示数组元素时,有一定的差异.
p=p+3;
此时,指针变量p指向第四个数组元素a[3],那么p[0]、*(p+0)等价于a[3],而*(p-1)、p[-1]等价于a[2];
*(p+1)、p[1]等价于a[4],依次类推.
【例5-3】输出一维数组中的所有元素。
inta[]={1,2,3,4,5},*p,i;
/*将数组a的首地址赋值给指针变量p*/
for(i=0;
i<
5;
i++)
\n%d,%d,%d,%d"
a[i],*(a+i),p[i],*(p+i));
}
程序分析与解释:
程序中的printf()函数展示了对一维数组元素的四种等价表示形式。
假如数组a的首地址是2000,那么p指向内存单元2000,则该数组在内存中存放形式及数组元素的表示形式如图5-4所示:
【例5-5】输入五个整数,使用指针变量将这五个数按从小到大排序后输出。
main()
{
inta[5],*pp,*p,*q,t;
for(p=a;
p<
a+5;
p++)/*输入5个整数,并且分别存放到数组a中*/
scanf("
%d"
p);
for(p=a;
a+4;
p++)/*使指针变量p指向数组a*/
pp=p;
for(q=p+1;
q<
q++)/*比较大小*/
if(*pp>
*q)
pp=q;
if(pp!
=p)/*如果本轮比较出的较小值不等于*p,那么交换值*/
t=*p;
*p=*pp;
*pp=t;
}
p++)
printf("
%d"
*p);
/*输出数组的值,并且每输出一个空一格*/
运行情况如下:
输入:
546512342
输出:
212345465
该问题的解决方法,采取的是在上一章所学的选择排序法来进行排序的.
5.2.2指向二维数组的指针
1.二维数组的地址
例如有定义语句:
inta[3][3];
✧二维数组名a是数组的首地址。
✧二维数组a包含三个行元素:
a[0]、a[1]、a[2]。
✧三个行元素的地址分别是:
a、a+1、a+2。
而a[0]、a[1]、a[2]也是地址量,是一维数组名,即*(a+0)、*(a+1)、*(a+2)是一维数组首个元素地址。
如图5-5:
2.二维数组元素的地址
a[0]、a[1]、a[2]是一维数组名,所以a[i]+j是数组元素的地址。
数组元素a[i][j]的地址可以表示为下列形式,&
a[i][j]、a[i]+j、*(a+i)+j
如图5-6所示:
3.二维数组元素的表示法
数组元素可用下列形式表示:
a[i][j]、*(a[i]+j)、*(*(a+i)+j)
a是二维数组,根据C的地址计算方法,a经过两次*操作才能访问到数组元素。
所以:
*a是a[0],**a才是a[0][0]。
看一看,想一想若a是二维数组名请理解下列表示方法的含义
a
a[0],*(a+0),*a,&
a[0][0]
a+1,&
a[1]
a[1]+2,*(a+1)+2,&
a[1][2]
a[0]是a[0][0]的地址,*a[0]是a[0][0]。
4.指向二维数组元素的指针变量
【例5-6】用指向数组元素的指针变量输出数组元素,请注意数组元素表示方法。
main()
{inta[3][4]={{0,1,2,3},{10,11,12,13},{20,21,22,23}},i,j,*p;
for(p=a[0],i=0;
i<
3;
i++)
{for(j=0;
j<
4;
j++)
%4d"
*(p+i*4+j));
/*元素的相对位置为i*4+j*/
\n"
);
此程序定义了一个二维数组a和一个指向整型变量的指针变量p。
并将数组首地址赋值给指针变量p,通过改变变量i,j的值,来输出数组元素的值,整个过程中,指针变量p的值没有发生改变。
5.2.3指向字符串的指针变量
C语言将字符串是作为数组对待的,与数值型数组一样,我们也可用字符型的指针变量指向字符串,然后通过指针变量来访问字符串存贮区域。
设有如下语句:
char*cp;
cp="
love”;
则cp指向字符串”love”常量的首字符’a’,如图5-7所示,程序中可通过cp来访问这一存贮区域。
1.指向字符串的指针变量的定义及初始化
【例5-7】通过初始化使指针指向一个字符串。
charstr1[]="
Goodmorning!
"
;
/*定义一个字符数组*/
char*str2="
Goodnight!
/*定义一个指向字符串的指针变量*/
%s\n"
str1);
str2);
C语言中对字符常量是按照字符数组来处理的,在内存中开辟了一个字符数组用来存放该字符串常量。
对字符指针str2进行初始化,实际上是把字符串的第一个元素的地址(即存放字符串的首地址)赋给了str2,见图5-8。
有人认为str2是一个字符串变量,认为在定义的时候把"
这几个字符赋给该字符串变量,这种认为是不对的。
定义指针变量str2部分:
char*str2="
等价于下面两行:
char*str2;
str2="
;
在输出时,要用:
printf(“%s\n”,str2);
%s是输出字符串格式控制符,在输出项要用字符指针变量名str2,则系统先输出指针所指向的字符,然后再自动使指针值加1,使之指向下一个字符,然后再输出一个字符,……,直到遇到字符串结束符’\0’为止。
注意,在内存中,字符串的最后都被自动加上了一个’\0’,如图5-8所示,因此在输出时能确定字符串的中止位置。
✧通过字符数组名或者字符指针变量可以输出一个字符串,而对数值型数组是不能企图用数组名输出它全部元素的,例如:
inta[3]={1,2,3};
printf(“%d”,a)
这种是错误的,数值型数组只能逐个元素输出。
✧对字符串中字符的存取,可以用下标法也可以用指针法。
【例5-8】已知字符串str,从中截取一子串。
要求该子串是从str的第m个字符开始,由n个字符组成。
【解题思路】
定义字符数组c存放子串,字符指针变量p用于复制子串,利用循环语句从字符串str截取n个字符。
考虑到几种特殊情况:
(1)m位置后的字符数有可能不足n个,所以在循环读取字符时,若读到‘\0’停止截取,利用break语句跳出循环。
(2)输入的截取位置m大于字符串的长度,则子串为空。
(3)要求输入的截取位置和字符个数均大于0,否则子串为空。
源程序如下:
main()
charc[80],*p,*str="
Thisisastring."
inti,m,n;
m,n="
%d,%d"
&
m,&
n);
if(m>
strlen(str)||n<
=0||m<
=0)
printf("
NULL\n"
else
for(p=str+m-1,i=0;
n;
i++)
if(*p)
c[i]=*p++;
else
break;
/*如读取到'
\0'
则停止循环*/
c[i]='
/*在c数组中加上子串结束标志*/
c);
5.3结构体
在前面,已经介绍过了基本数据类型的变量(如整型、字符型、浮点型变量),也介绍了一种构造类型数据——数组,其中数组中各元素属于同一种类型。
但是只有这些数据类型是不够的,有时需要将不同数据类型组合成一个有机的整体,以便引用。
这些组合在一个整体中的数据是互相联系的,例如:
一个学生的信息有学号、姓名、性别、年龄、住址、成绩等;
一本图书的信息有分类编号、书名、作者、出版社、出版日期、价格、库存量等。
那么如何描述这些类型不同的相关数据呢?
在C语言中允许用户自己指定这样一种数据结构,它是由若干个类型不同的(当然也可以相同)的数据项组合在一起,称为结构体(structure)。
构成结构体的各个数据项称为结构体成员。
它相当于其他高级语言中的“记录”。
如果要用C语言编写实用程序,结构的知识是不可缺少的!
同时,结构还是C++等后续语言的基础。
5.3.1结构体的定义与引用
1.结构体定义的一般格式
struct结构体名
数据类型1成员名1;
数据类型2成员名2;
……
数据类型n成员名n;
};
✧不要忽略了最后的分号。
✧struct为关键字;
✧结构体名是用户定义的类型标识,用作结构体类型的标志。
✧结