2021-08-27

C语言指针使用小结

指针是C语言中有趣而复杂的概念,一方面,指针为程序的灵活操作带来了方便,另一方面,指针也带来了一些难以预见的问题,这里对指针的使用做一个小结,方便大家梳理和复习相关概念。

1. 指针的概念

指针也就是内存地址,指针变量是用来存放内存地址的变量。指针变量声明的一般形式为:
variable type *pointer-name;
比如 int i_ptr;
注意:int
i_ptr 和int *i_ptr两种形式的写法都是一个含义,表示一个指向int 变量的指针.
这里引用一个网络上的一张图(from runoob)

在这里插入图片描述

2. 指针的算术运算

因为指针变量是表示内存地址的一个变量,既然是变量,就会存在变量值的运算,指针支持加、减、自增、自减四个运算.
注意:
指针的递增,它其实会指向下一个元素的存储单元。
指针的递减,它都会指向前一个元素的存储单元。
指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度.如:
int *p1;
p1++实际会增加int表示的数据长度(4 Byte).
char *p2;
p2++实际会增加char表示的数据长度(1 Byte).

3. 指针数组

简单说,指针数组就是一个数组,数组里面存储的是一个指针,至于指向什么数据类型的指针,则根据定义确定,比如:
int ptr[20];//定义一个指针数组,数组长度为20个,然后每个指针是指向int变量的指针.
那么,sizeof(ptr)=? 这就跟语言没有关系了,只是和系统的寻址能力有关,比如,在32位OS
下面就是4
20=80个字节,在64位操作系统下面就是8*20=160个字节了.

4.数组指针

刚才描述的是指针数组,意思是一个数组,数组的每一个元素都是一个指针.其定义形式如:
type *pointer[20];如果这么定义,那么就完全不一样了:
int (*pointer)[20], 具体分析如下:
int (*pointer)[20];其中()的优先级比[]大,因此,(*pointer)首先是一个指针,而前面的int表示数字里面存储的数值的类型(int类型),因此,int (*pointer)[20]是一个指针,这个指针指向一个int型的20个元素的数组的首地址.这就是数组指针(在这个定义下,这个数组是没有名字的).
因此,数组指针和指针数组的区别是显然的,一个是指向数组的指针,一个是数据类型为指针的数组.

5.指向指针的指针

当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置.

int a=100;
int *p1=&a;
int **p2=&p1; 

在这里插入图片描述
我们这里来简单讨论一个有意思的情况,这里,p1是一个指向int数据的指针.p2是指向p1的指针(指针的指针).如果我们这样定义 :

int a=100;
int *p1=&a;
int **p2=&p1; 
int *p3=&p1;

这里,在gcc下面是能编译通过的,但是会有一个告警:initialization of ‘int *’ from incompatible pointer type ‘int **’ [-Wincompatible-pointer-types].
其实,这里的含义就是:本来p2是一个指向了指针的指针,p3只是一个指向了int数据的指针,但是,如果你强行将&p1赋值给p3,那么意思就是:p3本来是一个指向int数据的指针,但是你强行将它指向了p1(一个指向了int a的指针),这就是一个强制类型转换.

6.指针作为函数参数

C语言中,函数参数有三种参数传递方式:
1)值传递,就是把你的变量的值传递给函数的形式参数,实际就是用变量的值来新生成一个形式参数,因而在函数里对形参的改变不会影响到函数外的变量的值。
小结:函数形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要利用外部参数而且不希望这个改变影响外部参数,则采用值传递.
2)地址传递,就是把变量的地址赋给函数里形式参数的指针,使指针指向真实的变量的地址,因为对指针所指地址的内容的改变能反映到函数外,能改变函数外的变量的值。
小结:希望通过函数修改外部参数,则指针传递(地址传递).
3)引用传递,实际是通过指针来实现的,能达到使用的效果如传址,可是使用方式如传值。
小结:形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作.

7.指向函数的指针(函数指针)和回调函数

指针表示所指向变量的地址,实际上,在C/C++语言中,函数就是一个地址,因此,指向函数的指针是完全成立且存在的. 举例如下:
int (*p)(int,int); 首先,(*p)表示p是一个指针,然后(int,int),表示这是指向函数的一个指针,这个函数有两个形式参数int,int,最后的int,表示返回值是int.
函数指针使得通过指针调用函数成为可能,同时,赫赫有名的回调函数,函数指针是其中的一个实现手段.
回调函数是由别人的函数执行时调用你实现的函数,要达到这个效果,你的实现函数必须通过函数指针的形式作为函数参数传入别人的函数.这是网上的某一张照片,拷贝过来如下:
在这里插入图片描述
从上图可以看出,main program calls library function, library function calls callback function(通过函数指针),可以达到一个效果就是:如果你想改变callback function的实现方式,你自己修改callback function 即可,主程序和library function都不需要改变.

8.小结

这里引用别人的一个对于指针的分析方式,比较浅显易懂,引用如下:
1.int p; – 这是一个普通的整型变量
*2.int p; – 首先从 p 处开始,先与 * 结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
3.int p[3] – 首先从 p 处开始,先与 [] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
4. int *p[3]; – 首先从 p 处开始, 先与 [] 结合,因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。
*5. int (p)[3]; – 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。
**6. int p; – 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
7. int p(int); – 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
*8. int (p)(int); – 从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
**9.int (p(int))[3]; – 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值