C语言程序设计 善于指针

1 什么是指针

内存区的每一个字节有一个编号,这就是内存单元的“地址”。
由于通过地址能找到所需的变量单元,地址指向该变量单元。
将地址形象化称为“指针”,意思是通过它能找到以它为地址的内存单元。

直接访问
例如定义整型变量a,编译系统在对程序编译时给变量a分配内存单元。每一个变量都有相应的起始地址,a的起始地址为2000,如果向变量a赋值,a=3,系统根据变量名a查出它相应2000,然后将整数3存放到起始地址2000的存储单元中,这种直接按变量名进行的访问,称为“直接访问”方式。如图:
在这里插入图片描述
间接访问
将变量a的地址存放在另一个变量中。通过另一个变量找到变量a的地址,从而访问变量a。
指针变量就是存放地址的变量。定义一个指针变量a_pointer, 将a地址(2000)存放到a_pointer中。
a_pointer=&a;

小结:
如果一个指针变量中存放了一个整型变量的地址,则称这个变量是指向整型变量的指针变量
指针是一个地址,指针变量是存放地址的变量。

2 指针变量

2.1 使用指针变量访问变量的例子

例题:通过指针变量访问整型变量。
编写程序和运行结果:
在这里插入图片描述
分析:
(1)int * pointer_1,*pointer_2;
int表示所定义的指针变量是指向整型变量的。“ * ”表示所定义的变量是指针变量
经此定义后,他们可以指向任何整型变量(或数组元素),但是不能指向其他类型的数据(如float,double,char类型的数据)。定义了的指针变量pointer_1和pointer_2,但pointer_1和pointer_2没有赋初值,它们并未指向任何一个实际的变量。
(2)pointer_1=&a; pointer_2=&b;
使pointer_1指向a,pointer_2指向b。如下图:
在这里插入图片描述
(3) * pointer_1和 * pointer_2就是变量a,b。“ * ”表示“指向对象”

2.2 怎样定义指针变量

一般形式:
基类型 * 指针变量名;
如:
在这里插入图片描述
左端的int是在定义指针变量是必须指定的“基类型”。指针变量的基类型用来指定此指针变量可以指向的变量的类型。例如:定义的基类型为int的指针变量pointer_1,只能指向整型变量i,j,a,b等,但不能指向浮点型变量a。
可以在定义指针变量是,同时对它初始化,如:
在这里插入图片描述
注意:
(1)指针变量名是pointer_1和pointer_2,而不是* pointer_1和 * pointer_2。因为a的地址是赋给指针变量pointer_1,而不是赋给* pointer_1(即变量a)。
(2)在定义指针变量时必须指定基类型。一个指针变量只能指向同一个类型的变量。
(3)变量的类型和指针变量的类型必须一致。
(4)指针变量中之能存放地址(指针),不能将一个整数赋给一个指针变量。如以下是错误示范
在这里插入图片描述
常数不能赋给指针变量。

2.3 怎样引用指针变量

在引用指针变量是,有3种情况:
(1)给指针变量赋值。如:
在这里插入图片描述
(注:必须先定义a为整型变量,pointer_1为指向整型变量的指针变量。)
赋值后指针变量pointer_1的值是变量a的地址,pointer_1指向a。
(2)引用指针变量指向的变量
在这里插入图片描述
其作用是以十进制整数形式输出指针变量pointer_1所指向的变量的值,即变量a的值。
(3)引用指针变量的值。如:
在这里插入图片描述
其作用是以八进制形式输出指针变量p的值,若p指向a,就输出a的地址。
例题:输入a和b两个整数,按先大后小的顺序输出a和b。
编写程序和运行结果:
在这里插入图片描述
分析:
该程序的核心是不交换整型变量的值,交换两个指针变量的值(即a和b的地址)。
输入a=5,b=9,由于a<b,将p1和p2的值交换,就是交换它们的指向。
交换前
在这里插入图片描述
交换后
在这里插入图片描述
指针代表的不仅是一个纯地址(即内存单元的编号),而且是一个带类型的地址。地址也是有类型的。
分析下面的定义:
在这里插入图片描述
定义了整型a和指向整型数据的指针变量p。
p存放a的地址,即p指向a。
&a,对整型变量a进行&操作(取地址操作)时,不仅得到a的纯地址(即内存编号,如2000),还得到a的类型(int)。总结,&a包含两个信息:纯地址和a的类型(即地址的基类型)。因此,地址2000应理解为内存编号为2000的纯地址以及它指向的数据类型。C语言中用的“地址”都是带类型的地址。

2.4 指针变量作为函数参数

函数的参数可以是指针类型的。它的作用是将一个变量的地址传送到另一个函数中。
例题:对输入的两个整数按大小顺序输出。要求用函数处理,用指针变量作函数参数。
思路:将两个变量的指针变量作为实参传递给形参的指针变量,在形参中通过指针交换两个变量的值
编写程序和运行结果:
在这里插入图片描述

分析:
核心是交换a和b的值,而p1和p2的值不变。
(1)swap是用户自定义的函数,它的作用是交换两个变量(a和b)的值。swap函数的两个形参p1和p2是指针变量。
(2)输入a和b,a和b的地址分别赋给pointer_1和pointer_2,pointer_1指向a,pointer_2指向b,如下图。
在这里插入图片描述
在这里插入图片描述

调用swap函数,实参pointer_1传给形参p1。p1和pointer_1都指向a,如下图。
在这里插入图片描述
在这里插入图片描述
执行swap函数体,使*p1和 *p2的值互换,也是a和b的值互换。互换后情况如下图,
在这里插入图片描述
函数调用结束后,形参p1和p2已释放,如下图,
在这里插入图片描述

3 通过指针引用数组

3.1 数组元素的指针

数组元素指针就是数组元素的地址。可以用一个指针变量指向一个数组元素。例如:
在这里插入图片描述
使p指向a数组的第0号元素。

在C语言中,数组名代表数组中首元素的地址
在这里插入图片描述
等价于
在这里插入图片描述
把a数组首元素的地址赋给指针变量p。

3.2 通过指针引用数组元素

引用一个数组元素,可以用两种方法:
(1)下标法,用数组名加下标,如a[i]形式。
(2)指针法。通过数组名计算出数字中序号为i的元素的地址,其形式为* (a+i);用一个指针变量p指向数组首元素,然后用 *(p+i)调用a数组中序号为i的元素。

例题:有一个数组存放10个学生的年龄,用不同的方法输出数组中的全部元素。
解题思路:先定义整型数组a[10],可以用下面几种不同的方法实现输出全部学生的年龄。①用数组名加下标找到所需要的数组元素;
②通过数组名计算数组元素地址,找到所需要的数组元素;
③通过指针变量计算数组元素地址,找到所需要的数组元素。
编写程序和运行结果:
在这里插入图片描述
指针运算:
通过指针的运算,可以方便地引用数组中的元素。
(1)如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。p+1所代表的地址实际上是p+1×d,d是一个数组元素所占的字节数,若p的值是2000,数组元素是float型,每个元素占4字节,则p+1的值为2004。
(2)如果指针变量p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址。
(3)*(p+i)或 *(a+i)是p+i或a+i所指向的数组元素,即a[i]。
(4)如果指针变量p1和p2都指向同一数组,若执行p2-p1,结果是两地址之差除以数组元素的长度。
(5)要注意指针变量的当前值,即组织变量当前指向哪一个元素,尤其要注意起始值。
(6)指向数组的指针变量也可以带下标,如p[i]。但是易出错,少使用。

3.3 用数组名作函数参数

实参数组名代表该数字首元素的地址,形参是用来接收从实参传递过来的数组首元素地址的。因此,形参是一个指针变量。例如:定义一个f函数,形参写成数组形式:
在这里插入图片描述

主函数为:
在这里插入图片描述
可以将形参arr按指针变量处理的,f函数的首部可以换成
在这里插入图片描述
*arr就是array[0]。arr+1指向array[1]。
*(arr+i)和arr[i]是无条件等价的。
arr[0]和 *arr以及array[0]都代表数组array序号为0的元素。

4 通过指针引用数组

4.1 字符串的表示形式

(1)用字符数组存放一个字符串,然后用字符数组名和下标访问字符数组中的元素,如a[i],也可以通过%s格式输出一个字符串。
(2)定义一个字符指针,用字符指针指向字符串中的字符。
例题:定义字符指针,使它指向一个字符串。
编写程序和运行结果:
在这里插入图片描述
分析:
(1)对字符指针变量string初始化,实际上是把字符串第1个元素的地址赋给string。
(2)
在这里插入图片描述
等价于
在这里插入图片描述
可以看到把string定义为一个指针变量,基类型为字符型。
(3)输出时,用
在这里插入图片描述
%s是输出字符串时所用的格式符, 在输出项中给出字符指针变量名string, 则系统先输出它所指向的一个字符数据, 然后自动使string加1, 使之指向下一个字符, 然后再输出一个字符……如此直到遇到字符串结束标志\0’为止。注意,在内存中,字符串的最后被自动加了一个\0,因此在输出时能确定字符串的终止位置。
在这里插入图片描述
例题1:有一个字符数组a,在其中存放字符串“Are you OK?”,要求把该字符串复制到字符数组b中。
思路:
从第一个字符开始.将数组a中的字符逐个复制到数组b中,直到遇到a数组中的某一元素值为’\0’为止。此时表示数组a中的字符串结束,然后在已复制到b数组中的字符最后加一个’\0’,表示字符串结束。
编写程序和运行结果:
在这里插入图片描述
分析:
(1)访问数组元素有两种方法。一,下标法。数组名加下标,如a[i]。二,地址法。如*(a+i)。因此,a[i]和(a+i)是无条件等价的*。a[0]和*(0)都表示数组a序号为0的元素。

例题2:有一个字符数组a,在其中存放字符串“Are you OK?”,要求把该字符串复制到字符数组b中,用指针变量来处理。
思路:
(1)设两个指针变量p1和p2,开始时分别指向字符串a和b的第1个字符;
在这里插入图片描述

(2)将p1指向的a串中的第1个字符复制到p2所指向的b串中的第1个字符位置;
在这里插入图片描述

(3)使p1和p2分别下移个位置,即分别指向串a和串b中的第2个字符;
在这里插入图片描述
(4)将p1指向的a串中的字符复制到p2所指向的b串中的位置;
在这里插入图片描述
(5)再使p1和p2分别下移一个位置;
在这里插入图片描述
(6)如此反复执行(4)和(5)直到发现p1指向的字符是’\0’为止,此时不再进行复制字符,而在p2所指的位置上赋一个’\0’字符。
在这里插入图片描述
编写程序:
在这里插入图片描述

运行结果:
在这里插入图片描述
分析:
p1和p2的值是不断在改变的,在for语句中的p1++和p2++使p1和p2同步移动。

4.2 字符指针作函数参数

在被调用的函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串。
例题3:有一个字符数组a,在其中存放字符串“Are you OK?”,把该字符串复制到字符数组b中。要求用函数调用实现。
编写程序和运行结果:
在这里插入图片描述

分析:
(1)copy_string函数的作用是将from[i]赋给to[i],直到from[i]的值等于’\0’为止。
(2)形参from和to是字符指针变量
(3)在调用copy_string函数是,将数组a首元素的地址传给from,把指针变量p的值(即数组b首元素的地址)传给to。因此,a[1]和from[1]是同一个单元,p[1](即b[1])和to[1]是同一单元。
(3)在for循环中,先检查from当前所指向的字符是否等于’\0’,如果不是,就执行to=from”, 每次将from赋给to,第1次就是将a数组中第1个字符赋给b数组的第1个字符。每次循环中都执行from++和to++, 使from和to分别指向a数组和b数组的下一个元素。下次再执行*to=*from时, 就将a[i] 赋给b[i] ……最后将’\0’赋给to。函数赋值过程,如下图。
在这里插入图片描述

(4)程序执行完以后,b数组的内容如下图。由于b数组原来的长度大于a数组,因此再将a数组复制到b数组后,未能全部覆盖b数组原有内容。b数组最后4个元素仍保留原状。在输出b时由于按%s(字符串)输出,遇\0’即告结束,因此第一个\0’后的字符不输出。
在这里插入图片描述
(5)将copy_string函数改写
在这里插入图片描述
在本程序中将“*to=*from”的操作放在while语句括号内的表达式中,而且把赋值运算和判断是否为’\0’的运算放在一个表达式中先赋值后判断。在循环体中使to和from增值, 指向下一个元素……直到 *from的值为’\0’为止。

还将copy_string函数改写为
在这里插入图片描述
把上面程序的to++和from++运算与 * to= * from合并, 它的执行过程是, 先将 * from赋给 * to, 然后使to和from增值。显然这又简化了。
还将copy_string函数改写为
在这里插入图片描述
当 * from不等与’\0’时,将 * from赋给 * to,然后使to和from增值。最后在to加结束符’\0’。

还将copy_string函数改写为
在这里插入图片描述
‘\0’的ASCII代码为0,所以关系表达式可以由(*from!=’\0’)改为(*from!=0),而( * from!=0)可以简化为(*from),因为 * from的值不等于0,则为真,程序执行函数体,同时 * from!=0为真,程序也执行函数体。

还将copy_string函数改写为
在这里插入图片描述
等价于
在这里插入图片描述
将 * from赋给 * to,然后使to和from增值,最后赋值后的 * to值等于’\0’,则循环结束。

还将copy_string函数改写为用for语句
在这里插入图片描述
等价于
在这里插入图片描述
也可以用字符数组名作函数形参,在函数中另定义两个指针变量p1,p2。函数copy_string可写成:
在这里插入图片描述

4.3 使用字符指针变量和字符数组的区别

(1)字符数组由若千个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第1个字符的地址),绝不是将字符串放到字符指针变量中。
(2)赋值方式。对字符数组只能对各个元素赋值,能用以下办法对字符数组赋值。
char str[20]={"Are you OK?"};
对字符指针变量,采用下面方法赋值。
在这里插入图片描述
等价于
在这里插入图片描述
小结:
数组可以在定义时整体赋值,但不能在赋值语句中整体赋值。
字符指针变量可以在定义时整体赋值,也可以在赋值语句中整体赋值。

5 提高部分

5.1 多维数组的指针

指针可以指向多维数组中的元素。例如二维数组,可以认为二维数组时数的数,即二维数组是由n个一维数组组成的。
在这里插入图片描述
a数组为3行4列的数组。a表示二维数组首元素的地址,现在的首元素是由4个整型元素组成的一维数组,因此,a表示的是0行(首行)的地址。a+1代表序号为1的行的首地址。
在这里插入图片描述

如果二维数组的首行的首地址为2000,若一个整型数据占4字节,则a+1的值应该是2000+4×4=2016.a+1指向a[1],a+1的值是a[1]的首地址。
在这里插入图片描述
可以定义指向一维数组的指针变量:
在这里插入图片描述
p只能指向一个包含4个元素的一维数组,p的值就是该一维数组的起始地址
赋值语句:
在这里插入图片描述
此时o指向数组序号为1的行(即a[1])的开头。

5.2 指向函数的指针

在程序定义一个函数,系统给这个函数分配一段存储空间,这段存储空间的起始地址成为这个函数的指针。
可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着此指针变量指向该函数。
定义一个指向函数的指针变量:
在这里插入图片描述
指针变量名为p,它指向函数的类型为int型,函数有两个int型的形参。
定义的一般形式为:
数据类型 (指针变量名)(函数参数表列);
若要用指针调用函数,必须先使指针变量指向该函数,如:
在这里插入图片描述
这就把max函数的入口地址赋给了指针变量p。
用函数指针变量调用函数时,只需将(
p)代替函数名即可(p为指针变量名),在( * p)之后的括号中写上实参。例如:
在这里插入图片描述
由p指向max函数,调用函数还可以写成
在这里插入图片描述

5.3 返回指针值的函数

一个函数可以返回指针型的数据,即一个地址。
例如:
在这里插入图片描述
a是函数名,这个函数名前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针是指向整型变量的, x和y是函数a的形参(整型) 。调用它以后能得到一个指向整型数据的指针(地址)。
返回指针值的函数一般定义形式为:
类型名 * 函数名(参数表列);
这种形式与定义指向函数的指针变量很相似,但请注意:在*a两侧没有括号。有括号时就成了指向函数的指针变量了。

5.4 指针数组

指针数组中的每一个元素都存放一个地址。例如:
在这里插入图片描述
p[4]是数组形式,它有4个元素。* 表示数组时指针类型。
一维指针数组的定义一般形式为:
数据类型 *数组名[数组长度];
指针数据比较适合用来指向若干个字符串。
例题:有若干本书,想把书名放在一个数组中。
在这里插入图片描述

采用二维数组的方法
二维数组中每一行包含的元素个数(即列数)相等。若按最长的字符串来定义列数,则浪费许多内存单元,如下图。
在这里插入图片描述
采用指针数组的方法
分别定义一些字符串,然后用指针数组中的元素分别指向各字符串。
在这里插入图片描述
如果想对字符串排序,不必改动字符串的位置,只需改动指针数组中个元素的指向即可。
在这里插入图片描述

5.5 多重指针——指向指针的指针

6 总结

(1)指针就是地址。指针变量是用来存放地址的变量。地址是一个值。
(2)地址就意味着指向,因为通过地址能找到具有该地址的对象.
(3)指针代表的不仅是一个纯地址(即内存单元的编号),而且还是一个带类型的地址。“指针就是地址”指的就是“带类型的地址”。
(4)搞清楚指针的指向。一维数组名代表数组首元素的地址,如:
在这里插入图片描述
p是指向int型类型的指针变是量, 显然, p只能指向数组中的首元素(int型变量) , 而不是指向整个数组。如p+1是指向下一个元素,而不是指向下一个数组。
对“p=a;”准确地说应该是:p指向a数组的首元素。同理,p指向字符串,也应理解为p指向字符串中的首字符。在进行赋值时一定要先确定赋值号两侧的类型是否相同,是否允许赋值。
(5)指针运算小结
①指针变量加(减)一个整数。例如:p++、p–、p+i、p-i、p+=i、p-=i等均是指针变量加(减)一个整数将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数相加(减)
②对指针变量赋值。将一个变量地址赋给一个指针变量。例如:
在这里插入图片描述
注意:不应把一个整数赋给指针变量
③指针变量可以有“空值”,即该指针变量不指向任何变量,可以这样表示:p=NULL;其中NULL是一个符号常量, 代表整数0。
在stdio.h头文件中对NULL进行了定义:
在这里插入图片描述

它使p指向地址为0的单元。系统保证使该单元不作他用(不存放有效数据),即有效数据的指针不指向0单元。
应该注意, p的值为NULL与来对p赋值是两个不同的概念。前者是有值的(值为0) ,不指向任何程序变量,后者虽未对p值但并不等于p无值,就是p可能指向一个事先未指定的单元。因此,在引用指针变量之前应对它赋值。
任何指针变量或地址都可以与NULL作相等或不相等的比较, 例如:
在这里插入图片描述

④两个指针变量可以相减。如果两个指针变量都指向同一个数组中的元素,则两个指针变量值之差是两个指针之间的元素个数。
⑤两个指针变量比较。若两个指针指向同一个数组的元素,则可以进行比较。指向前面的元素的指针变量“小于”指向后面元素的指针变量。如果p1和p2不指向同一数组则比较无意义。
(6)有关指针变量的定义形式的归纳比较,见下图。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45059457/article/details/114209739
今日推荐