彻底搞懂C语言指针(c语言 指针用法)
myzbx 2025-07-14 20:16 25 浏览
指针是C语言的难点,本篇文章总结一下各类指针的用法。
指针是个变量,它存储的是变量的地址,这个地址指向哪里,取决于指针的类型,指针类型包括以下几种:
基本类型指针
数组类型指针
函数类型指针
结构体类型指针
联合体类型指针
指针类型指针
无具体类型指针
下面阐述各个类型指针的使用方法。
一.基本类型指针
声明方式:
基本类型*p;
变量p是一个基本类型指针,p存储的是基本类型变量的地址,表示p指向了基本类型,基本类型包括char,short,int,float,long,double。
例如int*p,p就变成了一个整型指针,如下图
使用例子:
#include <stdio.h>
void swap(int* a, int* b);
int main(int args, char *argv[]) {
int a = 10;
int b = 20;
int*pa = &a;//获取变量a的地址赋值给指针pa
int* pb = &b;
printf("%i\n", *pa);//输出10
*pa = *pa + 1;
printf("%i\n", *pa);//输出11
int* clonep = pa;
printf("%i\n", *clonep);//输出11
clonep = pb;
printf("%i\n", *clonep);//输出20
int c = *pa;
printf("%i\n", c);//输出11
c = c + 1;
printf("%i\n", c);//输出12
printf("%i\n", *pa);//输出11
// 互换pa和pa指向的变量的值
swap(pa, pb);
printf("%i\n", a);//输出20
printf("%i\n", b);//输出11
}
// 互换a和b两个变量的值
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
从上面代码的例子,可以看出以下几点:
a.通过&运算符可以获取一个变量的地址,&运算符只能获取内存变量的地址,不能对常量或者寄存器变量进行&操作。
b.可以通过*p,获取指针p指向的变量的值。
c.函数调用时,会把传入的变量拷贝一份作为参数,这里的拷贝就是把变量的值拷贝了一份,自此后,参数就是一个新的变量,它与传入的变量的值相同,任何对参数值的修改,都不会影响到传入的变量。
指针也是变量,只不过指针的值是地址罢了,所以也可以把指针变量传入给函数作为参数(代码21行),函数调用时,会把传入的指标变量拷贝一份作为参数,这个参数就是一个新的指针变量,它与传入的指针变量指向了同一个地址,所以在函数内部可以对这个参数指针指向的变量进行操作,效果与传入的指针变量一样。
二.数组类型指针
一维数组类型指针的声明方式:
T (*p)[N];
变量p是一个数组指针,p存储的是一维数组T[N]的地址,表示p指向了一维数组,一维数组的长度为N,一维数组每一项的数据类型为T,T可以是基本类型,指针,结构体,联合体。
例如int (*p)[2] 表示p指向了一维整型数组,这个整型数组长度为2,如下图是一个二维数组a[3][2],这个二维数组由3个一维数组(每个一维数组有2个整型元素)组成。
使用例子
#include <stdio.h>
int main(int args, char *argv[]) {
int a[3][2] = { {1,2},{3,4},{5,6} };
int(*p)[2] = a;
printf("%i\n", **p++);//输出1
printf("%i\n", **p++);//输出3
printf("%i\n", **p);////输出5
p = &a[0];
printf("%i\n", **++p);//输出3
printf("%i\n", **++p);//输出5
printf("%i\n", **p);////输出5
}
上面代码例子定义了一个二维数组a[3][2]
第4行代码:int(*p)[2] = a;a是二维数组的首地址,它是一个常量,所以可以直接赋值给p,a与一维数组a[0]的地址相同即a=&a[0],此时p存储的就是一维数组a[0]的地址,指向了一维数组a[0]。
第5行代码:**和++运算都属于一元运算符,它们的优先级相同,但运算方向是从右向左的,因此先执行p++,而p++的意思是先操作p变量,然后再将p变量+1,这里操作p变量的意思是先执行**p,为什么需要两个*才能获取到二维数组的元素值呢,原因如下:
**p是从右向左执行的,先执行右边的*,因此先执行*p,*p的意思获取p指向的变量,而p指向的是一维数组,所以*p的结果就是a[0],a[0]是个一维数组,所以**p=*(a[0]),C语言规定,对数组可以执行*操作时,会临时转化为一个指针,因此可以a[0]就临时变成了一个指针,再次对a[0]进行*操作后,就获取到了a[0]这个一维数组第一个元素的值即1。
第6行代码:第5行代码p++后,p指向了a[1]的地址,如下图
类似于第5行代码的执行方式,此时**p++后,获取的变量值为a[1]的第一个元素即3
第7行代码:第6行代码p++后,p指向了a[2]的地址,如下图
因此执行**p后获取了a[2]数组的第一个元素即5。
第8行代码:第8行代码p = &a[0];获取第一个一维数组的地址,赋值给变量p。
第9行代码:printf("%i\n", **++p);先对p变量+1,执行后,p指向了a[1],然后再执行**就获取到了a[1]的第一个元素。
上述介绍的是一维数组指针,其实可以扩展到N维数组指针,声明方式如下:
T (*p)[N1][N2][N3]...[NN];
p为指向一个N维数组,这个N维数组存储的数据类型为T。
假设一个三维数组a[4][3][2],定义二维整型数组指针p,声明如下:
int (*p)[3][2];
p就是一个二维整型数组指针,p指向了二维数组,这个二维数组,有3行两列数据,每个存储单元存储的是整型数据,代码例子如下:
#include <stdio.h>
int main(int args, char *argv[]) {
int a[4][3][2] = {
{
{1,2},{3,4},{5,6}
},
{
{7,8},{9,10},{11,12}
},
{
{13,14},{15,16},{17,18}
} ,
{
{19,20},{21,22},{23,24}
}
};
int(*p)[3][2] = a;
printf("%i\n", ***p++);//输出1
printf("%i\n", ***p++);//输出7
printf("%i\n", ***p);////输出13
p = &a[0];
printf("%i\n", ***++p);//输出7
printf("%i\n", ***++p);//输出13
printf("%i\n", ***p);////输出13
}
好了,数组指针就介绍到这里了。
与数组指针比较类似的一个概念就是指针数组,指针数组是一个数组,数组的每个元素是个指针,这个指针可以是一个任何类型的指针,声明如下:
T *p[N];
p是个数组,这个数组长度为N,数组中的每个元素包含一个指向类型T的指针。
例如int *p[6],p是个数组,它的长度为6,每个数组存储的是整型指针,如下图
T可以是基本类型,结构体类型,联合体类型,指针类型等。
代码例子
#include <stdio.h>
int main(int args, char *argv[]) {
int a = 1;
int b = 2;
int c = 3;
int* pa = &a;
int* pb = &b;
int* parray[3] = {pa,pb,&c};
printf("%i\n", *parray[0]);//输出1
printf("%i\n", *parray[1]);//输出2
printf("%i\n", *parray[2]);//输出3
}
当然p也可以是N维数组,N维数组中存储的是指向T类型的指针,声明如下:
T *p[N1][N2][N3]....[NN];
现在来对比下:数组指针和指针数组的声明方式,看看有什么区别?
数组指针:T (*p)[N]
指针数组:T *p[N]
看起来声明方式比较类似,我们可以通过运算符优先级来分析上边的表达式,为什么是数组指针或者指针数组。
(),[]运算符的优先级大于*运算符,(),[]优先级相同,(),[]从左向右进行运算
先来看看T (*p)[N]
先执行(*p),这个执行后,表示p是一个指针,然后这个指针指向哪里呢?紧接着执行[N],表示这个指针指向了一个一维数组,这个一维数组的长度为N,最后的T表示数组中每个元素的类型。
T *p[N]
没有了(),[N]的优先级比*高,所以先执行p[N],表示p是一个长度为N的数组,数组里元素的类型是什么呢?,然后执行*p,表示数组中元素存储的是指针,最后的T表示指针指向的类型为T。
因此通过优先级分析,即使以后忘了,也可以通过这种方式回忆起来。
三.函数类型指针
声明如下:
T (*p)(参数1,.....参数n)
p存储的函数的地址,指向了函数,函数的返回类型为T,参数列表为参数1~参数n。
例如一个例子int (*p)(int a, int b),表示p是个函数指针,函数的返回类型为整型,参数列表为两个整型。
代码例子:
#include <stdio.h>
int swap(int i, int j);
int main(int args, char *argv[]) {
int a = 1;
int b = 2;
int (*f)(int i, int j);
f = swap;
printf("%i", (*f)(a, b));//输出3
printf("%i", f(a, b));//输出3
}
int swap(int i, int j) {
return i + j;
}
T (*p)(参数1,.....参数n)中T可以为基本类型,指针,结构体,联合体。
函数指针作为指针,同样也可以作为函数的参数,例子如下
#include <stdio.h>
int swap(int i, int j);
void test(int i, int j, int (*f)(int i, int j));
int main(int args, char *argv[]) {
int a = 1;
int b = 2;
test(a, b, swap);
}
void test(int i, int j, int (*f)(int i, int j)) {
printf("%i",(*f)(i,j));//输出3
};
int swap(int i, int j) {
return i + j;
}
第7行代码:swap是一个函数地址,是个常量,将这个函数地址传输给函数时,自动转化为函数指针。
好了,函数指针介绍到这里了。
有一个与函数指针容易混淆的概念就是返回指针的的函数,返回指针的函数的声明方式如下:
T *f(参数列表)
f是一个函数,它的返回类型为指向T的指针,T可以是基本类型,指针,结构体,联合体。
函数指针和返回函数的指针这两个概念容易混淆,不过可以根据运算符的优先级分析,快速得出一个表达式是函数指针还是返回函数的指针,如下所示
函数指针:T (*p)(参数列表n)
返回指针的函数: T *f(参数列表n)
先分析T (*p)(参数列表n),()的优先级比*高,()从左向右开始运算,因此先运算(*p),运算后,表明p就是一个指针,然而这个指针指向哪里呢?紧接着分析(参数列表n),分析(参数列表n)后,表明指针指向了一个函数,这个函数的参数列表就是参数列表n,然后再分析这个函数的返回类型,发现函数的返回类型为T,因此p就是一个指向函数的指针,函数的参数列表为参数列表n,返回类型为T。
再来分析T *f(参数列表n),()的优先级比*高,因此先运算f(参数列表n),表明f是一个函数,这个函数的参数列表为参数列表n,那么这个函数的返回类型什么呢?,紧接着分析*,表明返回的类型为一个指针,这个指针指向哪里呢?再分析T,这个指针指向T,因此f是一个函数,参数列表为参数列表n,返回值为指向T类型的指针。
四.结构体类型指针
声明方式如下:
struct 结构体类型 {
.............
}*p;
例如结构体声明如下
struct point {
int x=10;
int y=20;
} ;
那么结构体类型指针声明如下
struct point *p;p就是一个指向结构体类型point的指针。
例子如下
#include <stdio.h>
struct point {
int x;
int y;
};
int main(int args, char *argv[]) {
struct point p;
p.x = 10;
p.y = 20;
struct point* pp = &p;
printf("%i\n", p.x);//输出10
printf("%i\n", (*pp).x);//输出10
printf("%i\n", (*pp).y);//输出20
printf("%i\n", pp->x);//输出10
printf("%i\n", pp->y);//输出20
}
上述代码中->是C语言提供的结构体指针访问成员的快捷方式。
结构体类型指针,也可以作为函数参数和返回值
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
};
struct point* copy(struct point* p);
int main(int args, char *argv[]) {
struct point p;
p.x = 10;
p.y = 20;
struct point *copyp = copy(&p);
copyp->x = 30;
copyp->y = 40;
printf("%i\n", copyp->x);//30
printf("%i\n", copyp->y);//40
printf("%i\n", p.x);//10
printf("%i\n", p.y);//20
}
struct point *copy(struct point *p) {
struct point* clone = malloc(sizeof(struct point));
clone->x = p->x;
clone->y = p->y;
return clone;
}
第22行代码:通过malloc分配一个结构体point,point占用的空间在编译时已经能够确定,因此通过malloc创建一个结构体指针是很常见的一种方式。
五.联合体类型指针
联合体类型指针的申明方式如下:
union 联合体类型 {
.........
} *p;
下边为例子
#include <stdio.h>
#include <stdlib.h>
union tag* copy(union tag* p);
union tag {
int itag;
float ftag;
};
int main(int args, char *argv[]) {
union tag t;
t.itag = 2;
union tag* p = &t;
printf("%i\n", p->itag);
union tag *copyp = copy(p);//2
printf("%f\n", copyp->ftag);//12.200000
}
union tag*copy(union tag*p) {
union tag* clone = malloc(sizeof(union tag));
clone->ftag = p->itag + 10.2f;
return clone;
}
第16行代码:联合体也可以作为函数的参数,也可以作为函数的返回值。
六.指针类型指针
指针类型指针就是指向指针的指针。
声明格式如下:
T **p;
T类型可以是基本类型,结构体,联合体,指针。
例如char **p表示p指针指向字符指针,如下图
如上图所示p是个指针,它存储的指向char类型指针m的地址,m为一个指向字符的指针。
例子如下
#include <stdio.h>
#include <stdlib.h>
void print(char* chars[]);
int main(int args, char *argv[]) {
char* pc1 = "hello,world";
char* pc2 = "how old are you";
char** p1 = &pc1;
char* pca[2] = { pc1,pc2 };//pca是个数组,存储两个char*
printf("%s\n", *p1);//hello,world
printf("%s\n", pca[0]);//hello,world
printf("%s\n", pca[1]);//how old are you
printf("%s\n", *pca);////hello,world
print(pca);
}
void print(char* chars[]) {
printf("%s\n", *chars++);//hello,world
printf("%s\n", *chars);//how old are you
}
第8行代码:pca是一个数组,这个数组有2个元素,存储的指向char类型的指针。
第13行代码:pca是一个常量,C语言规定,一个数组传递给函数作为参数时,会自动转化为一个指针。
第16行代码:chars变成一个指针,这个指针指向了指针数组的首地址,如下图
七.无具体类型指针
声明格式如下:
void *
表明该指针不能确定指向哪里,因此可以将任何指针赋值给该类型的指针。
代码如下:
#include <stdio.h>
int main(int args, char *argv[]) {
char* pc = "hello,world";
void* pcvoid = pc;
printf("%s", (char*)pcvoid);//输出hello,world
}
番外篇
对指针可以增加限制符const
const T *const D
第一个const表示指针指向的内容不可以变,第二个const表示指针不能修改即指针变量的地址不能修改。
相关推荐
- OPPO Find X9手机曝料:6.6英寸屏幕、天玑9500芯片
-
IT之家8月27日消息,科技媒体xpertpick今天(8月27日)发布博文,报道称OPPO计划于今年10月推出FindX9系列旗舰手机,其中包括FindX9和...
- OPPO Find X9系列搭载影像新硬件,支持Ultra级画质和色彩还原
-
IT之家8月27日消息,OPPOFindX9系列手机发布时间逐渐临近,目前官方已开启新机的前瞻预热。OPPOFind系列产品负责人周意保今日发文解释了厂商为什么现在都喜欢跨界合作这一...
- 我回来了!聊聊屏幕对续航的影响_屏幕耗电吗
-
时隔一周终于回国,让大家久等了本来上周日就能到家,结果在旧金山转机的时候把护照弄丢了…幸好后来被一位黑人大姐找到了,才能顺利回国,感谢勤劳朴实的美利坚人民。出差途中笔记本的续航是很重要的,刚好联想的产...
- J人福音、P人救星,Lumix Flow如何重塑专业视频拍摄工作流
-
“等一下,刚才那个中景拍了没有?”“A机位的素材是哪一场的?”“完了,我忘了记哪一条是最好的了!”“今晚加个班,先把能用的素材挑出来……”作为经常一个人拍视频的内容创作者,这种崩溃称得上习以为常。如果...
- realme史上最窄边框和下巴 realme GT Neo3正式发布
-
中关村在线消息:今天下午14点,realme召开真我GTNeo3发布会。realmeGTNeo3搭载6.7英寸2412×1080OLED直屏,其支持120Hz刷新率,360Hz触控采样率,智能...
- 用酒精擦屏幕,对屏幕的伤害有多大?
-
天府新青年你触手可及的朋友圈附录:1.不是所有电脑的屏幕都不能用酒精来擦,通常来说只有镜面屏屏幕才有涂层,这种不能用酒精擦;而雾面屏用的是另外一种抗反射技术,这种一般擦了没事。镜面屏和雾面屏特别好认...
- windows11截屏快捷键是哪个?windows11快捷键设置大全
-
windows11键盘快捷方式就是键盘快捷方式就是按键或按键组合,可提供一种替代方式来执行通常使用鼠标执行的操作。下面就来分享下windows11截屏快捷键是哪个和windows11快捷键设置大全。一...
- 三星Galaxy S25 Slim配置曝光 6.7英寸屏幕搭配2亿像素主摄
-
【CNMO科技新闻】三星GalaxyS25系列将于北京时间1月23日正式发布,CNMO注意到有博主爆出了即将亮相GalaxyS25Slim的配置信息。据悉,GalaxyS25Slim将配备一...
- 两种手机屏幕到底有什么不一样?哪种手机屏幕更好?
-
一般来说,我们的手机屏幕只分为两种OLED和LCD,LCD是大火的一种手机屏幕,是千元机以及高端机的标配,OLED算是后起之秀,是近几年才渐渐兴起的一种类型的手机屏幕,那么这两种手机屏幕到底有什么不一...
- 有强芯才好用 这三款高性价比旗舰芯热机最低仅需1799元
-
在选购手机时,相信大家肯定都会把性能作为考虑的重点之一。而如果希望拥有出色的性能表现,一颗旗舰处理器是必不可少的。今天我就为大家汇总了几款采用旗舰处理器的底价新机,感兴趣的朋友千万不要错过。moto...
- 一文搞定FastDFS的搭建和使用_fastdfs怎么样
-
1.FastDFS概述FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文...
- 涨姿势!超级计算机用啥文件系统呢?
-
2015-10-1705:58:00作者:赵为民在计算机中,文件系统(filesystem)是一个非常重要的组件,你可以将他看做是操作系统的子系统,其实质就是一种软件的组件,通过文件系统我们可以...
- Window as a VM:Chrome OS 现可窗口化运行其它 Linux 分支
-
这世上纵然有多种办法可以在Chromebook上安装运行ChromeOS和其它Linux分支多系统,但如果无需重启通过引导切换,确实是个很酷的改进。Google布道师Francois...
- Win10新预览版19577开始推送:新图标+多项新功能
-
今日凌晨,微软正式向Windows10Insider快速通道用户推送了全新版本Windows10——Windows10InsiderPreviewBuild19577。19577版本是...
- 微软Windows升级密钥(例如家庭版升级为企业版)
-
下面的密钥,是微软官方提供的,仅能用于Windows10系统版本的升级,比如从家庭版升级为专业版、专业版升级为企业版等。升级密钥不能用于激活系统,激活需要KMS或者数字权利,由于涉及到版权问题,在此不...
- 一周热门
- 最近发表
-
- OPPO Find X9手机曝料:6.6英寸屏幕、天玑9500芯片
- OPPO Find X9系列搭载影像新硬件,支持Ultra级画质和色彩还原
- 我回来了!聊聊屏幕对续航的影响_屏幕耗电吗
- J人福音、P人救星,Lumix Flow如何重塑专业视频拍摄工作流
- realme史上最窄边框和下巴 realme GT Neo3正式发布
- 用酒精擦屏幕,对屏幕的伤害有多大?
- windows11截屏快捷键是哪个?windows11快捷键设置大全
- 三星Galaxy S25 Slim配置曝光 6.7英寸屏幕搭配2亿像素主摄
- 两种手机屏幕到底有什么不一样?哪种手机屏幕更好?
- 有强芯才好用 这三款高性价比旗舰芯热机最低仅需1799元
- 标签列表
-
- HTML 简介 (30)
- HTML 响应式设计 (31)
- HTML URL 编码 (32)
- HTML Web 服务器 (31)
- HTML 表单属性 (32)
- HTML 音频 (31)
- HTML5 支持 (33)
- HTML API (36)
- HTML 总结 (32)
- HTML 全局属性 (32)
- HTML 事件 (31)
- HTML 画布 (32)
- HTTP 方法 (30)
- 键盘快捷键 (30)
- CSS 语法 (35)
- CSS 轮廓宽度 (31)
- CSS 谷歌字体 (33)
- CSS 链接 (31)
- CSS 定位 (31)
- CSS 图片库 (32)
- CSS 图像精灵 (31)
- SVG 文本 (32)
- 时钟启动 (33)
- HTML 游戏 (34)
- JS Loop For (32)