百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

彻底搞懂C语言指针(c语言 指针用法)

myzbx 2025-07-14 20:16 3 浏览

指针是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表示指针不能修改即指针变量的地址不能修改。

相关推荐

JMeter:执行顺序与作用域(jmeter顺序执行怎么设置)

一、执行顺序类似于运算符或操作符的优先级,当JMeter测试中包含多个不同的元素时,哪些元素先执行,哪些元素后执行,并不是严格按照它们出现的先后顺序依次有序执行的,而是会遵循一定的内部规则,我们称之为...

彻底搞懂C语言指针(c语言 指针用法)

指针是C语言的难点,本篇文章总结一下各类指针的用法。指针是个变量,它存储的是变量的地址,这个地址指向哪里,取决于指针的类型,指针类型包括以下几种:基本类型指针数组类型指针函数类型指针结构体类型指针联合...

Excel运算符相关知识点分享(excel运算符有哪些类型)

在Excel中,运算符主要用于执行各种计算和逻辑操作主要分为以下四类1.比较运算符在Excel中,比较运算符用于比较两个值,并返回逻辑结果TRUE(真)或FALSE(假)。它们常用于条件判...

Python编程基础:运算符的优先级(python运算符优先级记忆口诀)

多个运算符同时出现在一个表达式中时,先执行哪个,后执行哪个,这就涉及运算符的优先级。如数学表达式,有+、-、×、÷、()等,优先级顺序是()、×、÷、+、-,如5+(5-3)×4÷2,先计算(5-3)...

吊打面试官(四)--Java语法基础运算符一文全掌握

简介本文介绍了Java运算符相关知识,包含运算规则,运算符使用经验,特殊运算符注意事项等,全文5400字。熟悉了这些内容,在运算符这块就可以吊打面试官了。Java运算符的规则与特性1.贪心规则(Ma...

C语言零基础教学-3-运算符与表达式

同学们好,今天学习c元基础知识第三讲:运算符与表达式。本节内容将学习算数运算符与算数表达式。·至臻至减运算符、赋值运算符、逗号运算符、求至结运算符。→首先学习算数运算符,它包含加减乘除求余数正负。比如...

Python运算符优先级终极指南:避免表达式计算的陷阱

混合表达式中的运算符优先级当Python表达式中同时出现算术运算符、布尔运算符和比较运算符时,计算顺序由运算符优先级决定:算术运算符(最高优先级)包括:乘方(**)、乘除(*,/,//,%)、加...

Python自动化办公应用学习笔记12——运算符及运算符优先级

一、运算符1.算术运算符:运算符名称描述示例+加数值相加10+3=13-减数值相减10-3=7*乘数值相乘10*3=30/除浮点数除法10/3≈3.33//整除向下...

python3-运算符优先级(python运算符优先级最高)

#挑战30天在头条写日记#Python运算符优先级以下列出了从最高到最低优先级的所有运算符,相同单元格内的运算符具有相同优先级。运算符均指二元运算,除非特别指出。相同单元格内的运算符从左至右分组...

Java运算符优先级表(java语言中运算符的优先级)

Java语言中有很多运算符,由于运算符优先级的问题经常会导致程序出现意想不到的结果,为了避免程序可能由于运算顺序而导致一系列的问题,Java初学者需应尽可能掌握这些运算符规律图示给大家详细介绍了运算符...

Excel公式中运算符类型及优先顺序

在Excel中公式中,用到的一些运算符是有优先计算顺序的,详见下图。下面我们简单介绍一下这些运算符的使用方法。说明:Excel中所有公式及运算符,都需要在英文输入法半角状态输入,不要输入中文字符或者全...

JavaScript基础知识14——运算符:逻辑运算符,运算符优先级

哈喽,大家好,我是雷工!一、逻辑运算符1、概念:在程序中用来连接多个比较条件时候使用的符号。2、应用场景:在程序中用来连接多个比较条件时候使用。3、逻辑运算符符号:4、代码演示逻辑运算符的使用:逻辑...

认识Excel中的运算符(excel中的运算符包括在哪里)

Excel中,函数与公式无疑是最具有魅力的功能之一。使用函数与公式,能帮助用户完成多种要求的数据运算、汇总、提取等工作。函数与公式同数据验证功能相结合,能限制数据的输入内容或类型,还可以制作动态更新...

JavaScript 中的运算符优先级(javascript中的运算符分为哪几种?)

#寻找热爱表达的你#新人求关注,点击右上角↗关注,博主日更,全年无休,您的关注是我的最大的更新的动力~感谢大家了运算符优先级在JavaScript中是指决定表达式中不同操作符执行顺序的规...

从几个细节问题出发,如何写好产品需求文档?

来人人都是产品经理【起点学院】,BAT实战派产品总监手把手系统带你学产品、学运营。这篇文章暂时不讨论什么是需求文档,也不强调需求文档的重要性等等,就简单地从各种细节问题出发如何写好一份需求文档。一份好...