C语言中的指针:掌握内存的钥匙
引言
C
语言是一种结构化编程语言,它提供了对硬件底层的直接访问,其中最强大的特性之一就是指针。指针允许程序员直接操作内存地址,这对于理解程序的内部工作原理以及优化代码性能至关重要。本文将深入探讨C语言中指针的概念、使用方法以及一些高级技巧。
什么是指针
指针就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其声明。
声明形式:
type *pointName;
其中type
是指针的基类型,它必须是一个有效的C
数据类型,pointName
是指针变量的名称。用来声明指针的星号*
与乘法中使用的星号是相同的,但是在c
语句中,星号是用来指定一个变量是指针。
例如:指针简单使用
输出结果
指针与变量
内存区的每一个字节都有一个编号,这就是"地址"
,如果在程序中定义了一个变量,在对程序进行编译运行时,系统就会给这个变量分配一个内存
单元,并确定它的内存地址也就是一串这个地址独有的编号,指针是内存单元的编号,指针变量是存放地址的变量,通常我们会把指针变量称为指针,其实指针与指针变量含义式不一样的。
指针变量赋值
指针变量在使用之前, 一定要先赋值,也就是让指针变量指向一个地址。注意,给指针变量赋值只能式地址,通过"&"符号来获取普通变量的地址。“&” - 取地址运算符,“*” - 指针运算符,或称为间接访问运算符,取地址的值。
例子:
输出结果:上面代码演示了如何在C语言
中使用指针,第一步定义一个整型变量i和一个指向整型变量的指针p
。然后将i的值初始化为10
,并将p
指向i的地址,然后打印出i
,和p
的内存地址以及i的值和通过指针p
访问到的值。
i = 0x7ffd55fc953c
变量i的存储地址, p = 0x7ffd55fc953c
是指针变量p指向的地址,以为代码中把i的地址赋给了p
,所以p
指向i
的存储地址。i=10
给i
赋值10
,*p = 10
指针变量p
指向地址里的值,p
指向的是地址0x7ffd55fc953c
,而这个地址里存储的值为10
,所以*p=10
。
通过地址取值:*((unsigned char *)p))
输出结果:((unsigned char *)7c3d726c))
代表把 0x7c3d726c
这个值,强制转换成一个无符号字符类型指针,相当于告诉编译器,这是一个地址,强制转换的类型和变量a
的数据类型保持一致,因为要通过地址,把变量i的值读出来,最后通过*
把这个地址的数据读出来,结果7c3d726c = 10
通过指针改变某个内存地址里的值
上面说了如何获取内存地址的值,那也可以通过指针改变某个内存地址里面的值。格式: *指针变量 = 数值; 【*p = 20】
输出结果:
案例2:输入a和b两个整数,然后按先大后小顺序输出a和b。不交换整型变量的值,而是交换两个指针变量的值。
分析:输入a=5,b=6,由于a<b,将p1和p2交换,注意的是,a和b的值并未交换,他们仍然保持原值,但是p1和p2的值改变了,p1的值原来为&a,后来变成了&b, p2的值原来为&b后来变成了&a,这样在输出p1和p2的时候,实际上是输出了变量b和a的值,所以先输出6后输出5。
指针变量作为函数参数
函数的参数不仅可以是整型,浮点型,字符型,还可以是指针类型,它的作用将一个变量的地址传送到另一个函数中。
案例:通过指针类型的数据作为函数的参数,对输入的两个整数按大小顺序输出。【通过指针实现交换两个变量的值】
案例:输入三个整数a,b,c,
要求按由大到小的顺序输出。
指针与一维数组
一个变量有地址,一个数组包含若干个元素,每个数组元素都在内存中占有存储单元,它们都有相应的地址,指针变量既然可以指向变量,当然也可以指向数组元素,把某一元素的地址放到一个指针变量中,所谓的数组元素的指针就是数组元素的地址。c语言定义数组时,编译器会分配连续地址的内存,来存储数组里的元素,实际上数组本质上也是指针。
分别打印了arr
和 &arr[0]
的地址,发现地址是一样的,既然是个地址,那就可以使用指针的形式,去访问地址里存储的值,然后打印*arr
的值,得到的结果为1
,正好和arr[0]
的值对应,最后得出数组也可以使用指针的形式去使用。
数组元素的引用
引用数组元素可以使用下标法,也可以使用指针法,即通过指向数组元素的指针找到所需的元素。使用指针法能使程序质量高,占用内存少,运行速度快。
p = &a; // p的值是a[0]的地址
p = a; // p的值是数组a首元素即a[0]的地址
注意:程序中的数组名不代表整个数组,只代表数组首元素的地址。上面的p = a
的作用是把a数组首元素的地址赋给指针变量p
,而不是把数组a
各元素的值赋给p
,可以简写为 int *p = &a[0];
也可以写成int *p = a;
也可以写成两行的形式int *p; p = &a[0];
他们都有一个作用:将a
数组首元素即a[0]
的地址赋给指针变量p
(而不是*p
)。
- 下标法
a[i]
的形式 - 指针法如
*(a+i)
或*(p+i)
。其中a
是数组名,p
是指向数组元素的指针变量。
或者用指针变量指向数组元素
注意:在使用指针变量指向数组元素的时候,可以通过改变指针变量的值指向不同的元素,例如上面代码中的方法是使用指针变量p
来指向元素,用p++
使p
的值不断改变宠儿指向不同的元素。
换一种想法,如果不用p
变化的方法而用数组名a
变化的方法如a++
行不行呢。for(p=a;a<(p+10);a++) printf("%d",*a);
答案是不行的,因为数组名a
代表数组首个元素的地址,他是一个指针型常量,它的值在程序运行期间是固定不变的,既然a
是常量,所以a++
是无法实现的。
例:通过指针变量输出整型数组a的10个元素。
很明显,输出的数值并不是a
数组中各个元素的值,因为在执行第二个for
循环读入数据后,p
已指向a
数组的末尾,因此在执行第二个for
循环时候,p的起始值不是&a[0]
了,而是a+10
。
解决上面问题只要在第二个for循环之前加一个赋值语句即可
常用指针引用数组元素的情况
- (1) p++; p; p++使p指向下一个元素a[i],然后再执行p,则得到下一个元素a[i]的值。
- (2) p++; 由于++和同优先级,结合方向为自右而左,因此它等价于*(p++)。先引用p的值,实现*p的运算,然后再使p自增1。
- (3) (p++)与(++p)作用,前者先取p值,然后使p加1。后者是先使p加1,再取p。若初始值为a即&a[0],若输出*(p++)得到a[0]的值,而输出*(++p)得到a[1]的值。
- (4) ++(*p) 表示p所指向的元素值加1,如果p = a,则++(*p)相当于++a[0],若a[0]的值为3,则在执行++(*p)即++a[0]后的值为4。
- (5) 如果p当前指向a数组中第i个元素a[i],则:
*(p--)
相当于a[i--]
,先对p进行运算(求p所指向的元素的值),再使p自减。
*(++p)
相当于a[++i],先使p自加,在进行运算。
*(--p)
相当于a[–i],先使p自减,再进行*运算。
将++和–运算符用于指针变量十分有效,可以使指针变量自动向前或向后移动,指向下一个或上一个数组元素。
在引用数组元素时指针的运算
在引入数组元素时会遇到指针的算术运算,当指针指向数组元素的时候,譬如指针变量p
指向数组元素a[0]
,我想用p+1
表示指向下一个元素a[1]
,如果能实现这种运算会对引用数组元素提供很大的便利。
在指针指向一个数组元素时可以进行以下运算
- 加一个整数用
+
或者+=
,如p+1
;- 减一个整数用
-
或者-=
,如p-1
;- 自加运算,如
p++
,++p
;- 自减运算,如
p--
,--p
;- 两个指针相减,如
p1-p2
如果p1
和p2
都指向同一个数组中的元素时才有意义。
解释:
(1)如果指针变量p已经指向数组中的一个元素,则p+1
指向同一数组中的下一个元素,p-1
指向同一数组中的上一个元素,执行p+1
时并不是将p
的值或地址简单的加1
而是加上一个数组元素所占用的字节数,例如数组元素是float
型,每个元素占4
个字节,则p+1
意味着p
的值是地址加上4
个字节,使它指向下一个元素,p+1
实际上代表p+1*d
,d
是一个数组元素所占的字节数。
(2)如果p
的值为&a[0]
,则p+i
和a+i
就是数组元素a[i]
的地址,或者说他们指向a数组序号为i
的元素。这里要注意的是a代表数组首元素地址,a+1
也是地址,它的计算方法同p+1
,即它的实际地址为a+1*d
。
(3)*(p+i)
或*(a+i)
是p+i
或a+i
所指向的数组元素,即a[i]
。实际上在编译的时候,对数组元素a[i]
就是按*(a+i)
处理的,即按数组首元素的地址加上相对位移量得到要找的元素地址,然后找出该单元中的内容。
(4)如果指针变量p1和p2都指向同意数组中的元素,如果执行p2-p1
,结果是p2-p1
的值两个地址之差,除以数组元素的长度。两个地址不能进行p1+p3
是毫无意义的。
- 指针的解引用:可以通过指针的解引用来访问它所指向的变量的值,解引用的操作符是"*",与乘法运算符不同。
例如:
int value = 10;
int *p = &value;
printf(“The value is %d”,*p); // 输出value的值- 指针的加减法:我们可以对指针进行加减操作,让指针移动到数组中的其他元素上。
int arrPtr = arr;
printf(“The second element is %d\n”,(arrPtr+1)); // 这里*(arrPtr+1)实际上就是指向数组第二个元素的地址,并解引用获取其值。
值得注意的是,指针算数的步长取决于指针所指向的数据类型。例如arrPtr指向的是一个整型的指针,那么arrPtr+1实际上是在当前地址基础上加上了整型的大小。- 指针 - 指针 :得到的数值的绝对值是指针和指针之间元素的个数。
其他指针使用方法:
指针与一维数组地址关系
指针与一维数组值的关系
用数组名做函数参数的情况
第一种,数组元素做实参的情况,假设swap
是将两个形参x,y
进行交换使用
swap(a[0],a[1]);
void swap(int x,int y);
得出:与用变量作为实参的情况一样,是”值传递“
方式,将a[0]
和a[1]
的值单向传递给x
和y
。当x
和y
的值改变时a[0]
和a[1]
的值不改变。
第二种: 数组名做函数形参的情况,实参数组名代表该数组首元素的地址,而形参是用来接受实参传递过来的数组首元素地址的。因此形参应该是一个只恨变量,只有指针变量才能存放地址,实际上c语言的编译都是将形参数组名作为指针变量来处理的。
void fun(int arr[],int n); === void fun(int *arr,int *n);
在函数被调用时,系统会在fun
函数中建立一个指针变量arr
,用来存放从主调函数传递过来的实参数组元素的地址。如果在fun
函数中用运算符siezof
确定arr所占字节数,可以发现sizeof(arr)
的值为8
,这就这个名了系统把arr
作为指针变量来处理的。当arr
接受了实参数组的首元素地址后,arr
就指向实参数组的首元素,也就是指向了brr[0]
。因此,arr
就是brr[0]
。
注意: 实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。在函数调用进行虚实结合后,形参的值就是实参数组首元素的地址,在函数指向期间,它可以在被赋值。
常用这种方法通过调用一个函数来改变实参数组的值。
第三种:变量名做函数参数和用数组名做函数参数比较
- 1.当实参类型是变量名时,要求形参类型也是变量名,通过形参传递的信息是变量的值,通过函数调用不能改变实参量的值。
- 2.当实参类型是数组名时,要求形参的类型是数组名或者指针变量,通过形参传递的信息是实参数组首元素地址,通过函数调用能改变其实参变量的值。
总结 :说明c语言调用函数时虚实结合的方法都是采用“值传递”方式,当变量名作为函数参数时传递的是变量的值。当用数组名作函数参数的时候,由于数组名代表的是数组首元素的地址,因此传递的值是地址,所以要求形参为指针变量。
第四种:数组名和指针变量作为函数的形参,在c语言
中,用下标法和指针法都可以访问一个数组,如果有一个数组a
,则a[i]
和*(a+i)
无条件等价,用数组名作形参,以便于实参数组对应,比较直观便于理解。从应用的角度看,用户可以认为有一个形参数组,他从实参数组哪里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就改变了实参数组的值,在主调函数中就可以利用这些已经改变的值。
例:将数组a
中n
个整数按相反顺序存放,用一个函数reversal
来实现交换。实参用数组名a
,形参可用数组名,也可以用指针变量。
上面代码中,在main
函数中定义整型数组a
,并赋初值,函数reversal
形参数数组名为x。在定义reversal
函数时,可以不指定形参数组x
的大小。因为形参数组名实际上是一个指针变量,并不是真的开辟一个数组空间。reversal
函数的形参n用来接受需要处理的元素个数。在main
函数中有函数调用语句reversal(a,10);
表示要求将a
数组的10
个元素颠倒排列。
改写上面代码 ,将reversal
中的形参改成指针变量,函数reversal
的形参有数组名x[]
变为指针变量*x
,相应的实参仍是数组名a
,即数组a
首元素的地址,将它传递给形参指针变量x
,这个时候x
就指向x[0]
。x+m
是a[m]
元素的地址。
总结归纳:
例:用选择排序+指针方法对是个整数由大到小顺序排序:
通过指针引用多维数组
指针变量可以指向一维数组中的元素,也可以指向多维数组中的元素,多维数组的指针比一维数组的指针要复杂一些。
多维数组元素的地址
以二维数组为例:int a[3][4] = {
{1,3,5,7},{9,11,13,15},{17,19,21,23}}
,a
是二维数组名,a
数组包含三行,即三个元素,每一行元素又是一个一维数组,它包含4
个元素即4
列元素。可以看出二维数组是数组的数组,即二维数组a
是由3个
一维数组组成的。
从二维数组角度来看,a
代表二维数组首元素的地址,现在的元素不是一个简单的整型元素,而是由四个整型元素所组成的一维数组,因此a
代表的是首行即序号为0
的行的起始地址。a+1
代表序号为1
的行的起始地址。如果二维数组的首行的起始地址为2000
,一个整型数据占4
个字节,则a+1
的值应该是2000+4*4=2016
因为第0
行有4
个整型数据。a+1
指向a[1]
,或者说a+1
的值是a[1]
的起始地址。a+2
代表a[2]
的起始地址,它的值是2032
。
上面说了如何表示首行地址,那么a[0]
是一维数组名,该一维数组中序号为1
的元素的地址显然应该用a[0]+1
来表示。此时a[0]+1
中的1
代表一列元素的字节数,即4
个字节。a[0]
的值是2000
,a[0]+1
的值是2004
而不是2016
,这是因为现在在一维数组范围内讨论问题的,正如有一个一维数组x
,x+1
是其第一个元素x[1]
的地址一样。a[0]+0
,a[0]+1
,a[0]+2
,a[0]+3
分别是a[0][0]
,a[0][1]
,a[0][2]
,a[0][3]
元素的地址,即(&a[0][0],&a[0][1],&a[0][2],&a[0][3])
。
一维数组与指针的时候已经说了a[0]
和*(a+0)
无条件等价,a[1]
和*(a+1)
无条件等价,a[i]
和*(a+i)
无条件等价。因此a[0]+1
和*(a+0)+1
都是&a[0][1]
。a[1]+2
和*(a+1)+2
的值都是&a[1][2]
。但是要注意不要将*(a+1)+2
错写成*(a+1+2)
,后者相当于a[3]
。
再深一步解析,既然a[0]+1
和*(a+0)+1
是a[0][1]
的地址,那么*(a[0]+1)
就是a[0][1]
值。同理,*(*(a+0)+1)
或*(*a+1)
也是a[0][1]
的值。*(a[i]+j)
或*(*(a+i)+j)
是a[i][j]
的值。*(a+i)
和a[i]
是无条件等价。
a[i]
从形式上看是a数组中序号为i的元素。如果a
是一维数组名,则a[i]
代表a
数组序号为i的元素存储单元。a[i]
是一个有确定地址的存储单元。但是如果a
是二维数组,则a[i]
是一维数组名,它只能是一个地址,并不代表一个存储单元,也不代表存储单元中的值如同一维数组名只有一个指针常量一样。a,a+i,a[i],*(a+i),*(a+i)+j,a[i]+j
都是地址,而*(a[i]+j)
和*(*(a+i)+j)
是二维i数组元素a[i][j]
的值。
下面是二维数组常用a的有关指针:
根据上面解释,输出二维数组的有关数据【地址和元素的值】
案例:根据输入数组下标,显示二维数组中的值
用指向数组的指针作为函数参数
一维数组名可以作为函数参数,多维数组名也可以做函数参数,用指针变量作形参,以接受实参数组名传递来的地址,两种方法:
i. 用指向变量的指针变量。
ii. 用纸箱一维数组的指针变量。
例:一个班级,三个学生,各4门课,计算总平均分数以及第n个学生的成绩。
代码解释:先调用average
函数求平均值。在函数average
中,形参p
被升为float *
类型指向float
型变量的指针变量,它的基类型是float
型,实参用*score
,即score[0]
也就是&score[0][0]
,即score[0][0]
的地址。把score[0][0]
的地址传给p
,使p
指向score[0][0]
。然后在average
函数中使用p
先后指向二维数组的各个元素,p
每加1
就改为指向score
数组的下一个元素。
函数search
的形参p的类型是float(*)[4]
,他不是指向整型变量的指针变量,而是指向包含4
个元素的一维数组的指针变量。函数开始调用时,将实参score
的值也就是数组0
行的起始地址传给p
,使p
也只想score[0]
。
注意:实参与形参如果是真真类型,应当注意他们的基类型必须一致,不应把int *型的指针即数组元素的地址传递给int(*)[4]
型(指向一维数组)的指针变量,反之亦然。
例:在上面的基础上,查找有一门以上课程不及格的学生,输出他们的全部课程的成绩。
代码分析:实参score
和形参p
的类型是相同的。在调用search
函数时,p
得到实参score
的值,即score[0]
的起始地址,也就是说p
也指向score
数组的第一行。然后p
先后指向隔行包括每行学生的几门课的成绩。
通过指针引用字符串
字符串的引用方式
在c语言中,字符串是存放在字符数组中的,像引用字符串有两种方法
- 用字符数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,也可以通过数组名和格式声明
%s
输出该字符串。
实际上string[7]
就是*(string+7)
,string+7
就是一个地址,它指向字符“C”
. - 用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
在上面代码中没有定义字符数组,只定义了一个char *型
的字符指针变量string
,用字符串常量"I love China!"
对它初始化。c语言
对字符串常量是按字符数组处理的,在内存中开辟一个字符数组用来存放该字符串常量,但是这个字符数组是没有名字的,因此不能通过数组名来引用,只能通过指针变量来引用。
注意: 有人误认为string
是一个字符串变量,以为在定义时把"I love China!"
这几个字符赋给该字符串变量,这是不对的,在C语言
中只有字符变量,没有字符串变量。string
被定义一个指针变量,基类型为字符型。它指能指向一个字符类型数据,而不能同时指向多个字符数据,更不是把I love China!
这些字符存放到string
中,也不是把字符串赋给*string
,只是把I love China!
的第一个字符的地址赋给指针变量string
。
例:将a串复制为字符串b,然后输出字符串b。
程序分析:程序中a和b
都定义为字符数组,通过地址访问其数组元素。在for
语句中先检查a[i]
是否为'\0'
。如果不等于'\0'
,表示字符串尚未处理完成,就将a[i]的值赋值给b[i]
,即复制一个字符串。
例:将a
串复制为字符串b
,然后输出字符串b。(用指针变量来处理)
代码解析 :p1
和p2
时指向字符型数据的指针变量,现使p1
和p2
分别指向字符串a
和b
的第一个字符。*p1
最初的值时字母“I”
。赋值语句*p2 = *p1
的作用是将字符串I赋值给篇所指向的元素,即b[0]
。然后p1
和p2
分别加上1
,分别指向其下面的一个元素,知道碰到'\0'
为止。
字符指针作函数参数
如果想把一个字符串从一个函数"传递"到另一个函数,可以用地址传递的办法,即用字符数组名作参数,也可以用字符指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。
- 字符数组名作为函数参数
例:用函数调用实现字符串的复制,用字符数组名作为函数参数。
程序分析:a
和b
时字符数组,copy_string
函数的作用是将from[i]
赋给to[i]
,直到from[i]
的值等于'\0'
为止。 - 用字符型指针变量作实参
分析: 指针变量from
的值时a
数组首元素的地址,指针变量to
的值时b
数组的首元素的地址。他们作为实参,把a
数组首元素的地址和b数组首元素的地址传递给形参数组名from
和to
(它们实质上也是指针变量)。 - 用字符指针变量作形参和实参
分析: 形参使用char *
类型变量即字符指针变量,main
函数中a
时字符指针变量,指向字符串"I am a teacher"
的首字符。b
时字符数组,在其中存放了字符串"You arr a student."
。p
是字符指针变量,它的值是b
数组第一个元素的地址,因此也指向字符串"You are a student."
的首字符。copy_string
函数的形参from
和to
是字符指针变量,再调用copy_string
的时候,将数组a
首元素地址传递给from
,把指针变量p
的值即数组b元素的地址传给to
。因此form
指向a
串的第一个字符a[0]
,to
指向b[0]
,在for
循环中,先检查from
当前所指向的字符是否为'\0'
,如果不是,表示需要复制此字符,就执行*to = *from
,每次将*from
的值赋给*to
,一致遇到'\0'
结束。
其实上面的字符串复制还可以写很多形式,如下
输出结果都是
![C语言中的指针(https://i-blog.csdnimg.cn/direct/6bdbe065eafa45ce928478f034eccabb.png)
调用函数时实参与形参的对应关系
使用字符指针变量和字符数组的比较
用字符数组和字符指针变量都能实现字符串的存储和运算,但它们二者之间是有区别的,不应混淆,主要区别如下:
- a.字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),绝不是将字符串放到字符指针变量中。
- b.赋值方式,可以对字符指针变量赋值,但是不能对数组名赋值。
- c.初始化的含义,对字符指针变量赋初始值,数组可以在定义时对个元素赋初始值,但不能用赋值语句对字符数组中全部元素整体赋值。
- d.存储单元的内容。编译时字符数组分配若干个存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元。
- e.指针变量的值是可以改变的,而字符数组名代表一个固定的值(数组元素的地址)不能改变。
- f.字符数组中各元素的值是可以改变的(可以不对他们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对他们再赋值)。
- g.引用数组元素,对字符数组可以使用下标法(用数组名和下标)引用一个数组元素,也可以使用地址法(如
*(a+5)
)引用数组元素a[5]
,如果定义了字符指针变量p
,并使它指向数组a的首地址,则可以用指针变量带下标的形式引用数组元素(如p[5]
),同样可以用地址法(如*(p+5)
)引用数组元素a[5]
,但是如果指针变量没有指向数组,则无法用p[5]
或*(p+5)
这样的形式引用数组中的元素。这是若输出p[5]
或者*(p+5)
这样的引用形式引用数组中的元素。这时若输出p[5]
或*(p+5)
,系统将输出指针变量p
所指的字符后面5
个字节的内容。- h.用指针变量指向一个格式字符串,可以用它们替代
printf函数
中的格式字符串。
因此只要改变指针变量format所指向的字符串,就可以改变输入输出的格式。这种printf函数称为可变格式输出函数。如:
因此,用指针变量指向字符串的方式更为方便。
指向函数的指针
什么是函数的指针
如果在c
中定义了一个函数,在编译时会把函数的源代码转换为可只想代码并分配一段存储空间。这段内存存储空间有一个起始地址,也就是函数的入口地址,每次调用函数时都会从该地址入口开始执行此段函数的代码,函数名代表函数的其实地址,调用函数时从函数名导函数的其实地址,并执行函数代码。
函数名就是函数的指针,代表着函数的起始地址,可以定义一个指向函数的指针变量,用来存放某一函数的起始地址,这就意味着指针变量指向该函数。如(int *p)(int,int);
定义p
是一个指向函数的指针变量,它可以指向函数类型为整型且有两个整型参数的函数。此时指针变量p
的类型用int(*)(int,int)
表示。
用函数指针变量调用函数
如果想调用一个函数,除了可以通过函数名调用外,还可以通过指向函数的指针变量来调用该函数。
例:用函数求整数a和b中的最大值。
通过指针变量调用它所指向的函数
上面两段代码,其中int(*p)(int,int);
用来定义p
是一个指向函数的指针变量,最前面的Int
表示这个函数返回值是整型的,最后面的括号中有两个int
,表示这个函数有两个int
型参数。注意,*p
两侧的括号不可省略,表示p
先于*
结合,是真真变量,然后在于后面的"()"
结合,"()"
表示是函数,即刻指针变量不是指像一般的变量,而是指向函数。
如何定义和使用指向函数的指针变量
类型名 (*指针变量名)(函数参数列表);
如:int(*p)(int,int); // 这里的类型名是值函数返回值的类型。
- 定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。如
int (*p)(int)(int);
表示指针变量p可以指向函数返回值为整型且有两个整型参数的函数。
- 如果要用指针调用函数,必须先使用指针变量指向该函数。如
p = max;
- 在给函数指针变量赋值的时候,只需给出函数名而不必给出参数,如
p = max;
,因为是将函数入口地址赋给p
,而不牵涉实参与形参的结合问题。如果写成p = max(a,b)
就错误了,p =max(a,b);
的作用是将调用max
函数所得到的函数值赋给p,而不是将函数入口地址赋给p。
- 用函数指针变量调用函数时,只需将
(*p)
代替函数名即可(p
为指针变量名),在(*p)
之后的括号中根据需要写上真实实参。如c = (*p)(a,b);
表示调用由p指向的函数,实参为a,b
。得到的函数值赋给C
,需要注意的是函数返回值的类型。从指针变量p
的定义中可以知道,函数的返回值应该是整型的,因此将其值赋给整型变量c
是合法的。
- 对指向函数的指针变量不能进行算术运算,如
p+n,p++,p--
等运算是无意义的。
- 函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
例:输入两个整数,然后让用户选择1或2,选1时调用max函数,输出二者中的大数,选2时调用min函数,输出二者中的小数。
用指向函数的指针作为函数参数
指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其他函数。指向函数的指针剋作为函数参数,把函数的入口地址传递给形参,这样就能够在被调用的函数中使用实参函数。
原理:有一个fun
函数,它有两个形参x1,x2
,定义x1,x2
为指向函数的指针变量,在调用函数fun
的时候,实参为两个函数名f1,f2
,给形参传递的是函数f1
和f2
的入口地址。这样在函数fun
中就可以调用f1
和f2
函数了。
在fun
函数中声明形参x1
和x2
为指向函数的指针变量,x1
指向的函数有一个整型形参,x2
指向的函数有两个整型形参。i
和j
是调用f1
和f2
函数时所要求的实参,函数fun
的形参x1
和x3
指针变量在函数fun
为被调用时不占内存单元,也不指向任何函数。在主函数调用fun
函数时,把实参函数f1
和f2
的入口地址传给形参指针变量x1
和x2
,使x1
和x2
指向函数f1
和f2
,这时候在函数fun
中用*x1
和*x2
就可以调用函数f1
和f2
。(*x1)(i)
就相当于f1(i)
。
例:有两个整数a和b,由用户输入1,2或3。如果输入1,程序就给出a和b中的大者,如果输入2就给出a和b中的小者,输入三求a和b的和。
程序分析:在定义fun
函数时,在函数首部使用int(*p)(int,int)
声明形参p是指向函数的指针,该函数时整形函数,有两个整型形参,max
,min
和add
都是已经定义的三个函数,分别用来实现求大数,小数,求和的功能。
返回指针值的函数
一个函数中可以返回一个整型值,字符串,实型值等, 也可以返回一个指针型的数据,即地址,概念与往常类似,只是返回值的类型时阵阵而已。如: int *a(int x,int y);
,a
时函数名,调用它以后能得到一个int *型
(指向整型数据)得指针,即整型数据的地址。x
和y
时函数a的形参微整型。
定义返回指针的函数原型一般形式:
类型名 *函数名(参数列表)
例:有a个学生,每个学生有b门课程的成绩,要求在用户输入学生序号以后,能输出该学生的全部成绩,用指针函数来实现。
函数search
定义为指针型函数,它的形参p是指向包含4
个元素的一维数组的指针变量,p+1
指向score
数组序号为1
的行,*(p+1)
指向1
行0
列元素。search
函数中的p是指针变量,它指向float
型便令而不是指向一维数组。main
函数调用search
函数,将score
数组进行首行地址传给形参p
(注意score
也是指向行的指针,而不是指向列元素的指针)。k
是要查找的序号,调用search
函数后,main
函数得到一个地址&[k][0]
指向k
个学生第0
门课程,赋给p
,然后此学生的四门课程成绩输出。
例:对于上面的学生,找出其中不及格课程的学生及其学号。
程序分析:函数search
的作用是检查一个学生有无不及格的课程,在search
函数中的pointer
是指向一维数组有四个元素的指针变量。pt
指向float
类型变量的指针变量,从实际参数传给形参p
的是score+i
,它是score
第i
行的首地址。
从search
中,先使pt = NULL
,即pt = 0;
用pt
作为区分有无不及格课程的标志。若经过检查四门课中有不及格的,就使pt
只想本行0
列元素,即pt=&score[i][0];
若无不及格则保持pt
的值为NULL
。将pt
返回main
函数,在main
函数中,把调用search
得到的函数值指针变量赋值给p
。用if
判断p
是否等于*(score+i)
,若相等,表示所查的序号为i
的学生有不及格的课程,p
的值为*(score+i)
,即p指向i
行0
列元素,就输出有不及格课程学生的四门课成绩,若无不及格,p
的值为null
不输出。
指针数组和多重指针
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中每一个元素都存放一个地址,相当于一个指针变量。如:int *p[4];
由于[]比优先级高,因此p
先于[4]
结合,形成p[4]
形式,这显然是数组形式,表示p
数组有四个元素。然后再与p
前面的结合,*表示此数组是指针类型的,每个数组元素相当于一个指针变量都可以指向一个整型变量。注意(*p)[4]
是指向一维数组的指针变量。
定义一维数组的一般形式:
类型名 * 数组名[数组长度]; ,类型名中应包括符号*,如 int *表示是指向整型数据的指针类型。
例子:将若干字符串按字母顺序由小到大输出。
程序分析:在main
函数中定义指针数组name
,他有五个元素,初始值分别是五个字符串的首字符的地址,sort
函数作用是对字符串排序,sort
函数的形参name
也是指针数组名,接受实参传过来的name
数组首元素即name[0]
的地址。因此形参name
数组和实参name
数组指的是同一数组,用选择法对字符串排序,strcmp
是系统提供的字符串比较函数,name[k]
和name[j]
是第k
个和第j
个字符串首字符的地址。strcmp[name[k],name[j])
的值如果name[k]
所指的字符串大于name[j]
所指的字符串,则此函数值为正值,若相等,则函数值为0
,若小于则函数值为负值,if
语句的作用是将两个串中的小的那个串的序号k
或j
之一保留在变量k中。当执行完for
循环后,从第i串到第n串这些字符串中,第k串最小,若k!=i
就表示最小的串不是i
串。故将name[i]
和name[k]
对换,也就是将指向第i个字符串的数组元素的值与第k
个字符串的元素的值兑换,也就是把他们的指向互换。
指向指针数据的指针变量
已经了解了指针数组,也需要了解指向指针数据的指针变量,简称为指向指针的指针。如下图可以看到name
是一个指针数组,它的每一个元素是一个指针型变量,其值为地址,name
既然是一个数组,它的每一元素都应有相应的地址。数组名name
代表该指针首元素的地址。name+i
是name[i]
的地址。name+i
就是指向指针型数据的指针。还可以设置一个指针变量p
,它指向指针数组的元素,p
就是指向指针类型数据的指针变量。
持续更新中。。。