关键词:
二级指针 (多级指针)
指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。,我们先看看二级指针,它们关系如下:
int a =100;//一个普通变量
int *p1 = &a;//一个一级指针p1指向a变量的地址
int **p2 = &p1;//一个二级指针p2指向p1指针的地址
// p2 -> p1 -> a
// &p1 &a 100
/*规律:
一级指针 指向变量的地址
二级指针 指向一级指针的地址
三级指针 指向二级指针的地址
依次类推....
指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*。p1 是一级指针,指向普通类型的数据,定义时有一个*;p2 是二级指针,指向一级指针 p1,定义时有两个*。
多级指针的话就是:
int ***p3 = &p2;//三级指针
int ****p4 = &p3;//四级指针
int *****p5 = &p4;//五级指针
//实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。
想要获取指针指向的数据时,一级指针加一个*,二级指针加两个*,三级指针加三个*关系如下:
#include <stdio.h>
int main()
int a = 100;
int *p1 = &a;
int **p2 = &p1;
int ***p3 = &p2;
printf("%d, %d, %d, %d\\n", a, *p1, **p2, ***p3);//他们的值都是一样的
printf("&p2 = %#X, p3 = %#X\\n", &p2, p3);//所指向的地址也是一样的
printf("&p1 = %#X, p2 = %#X, *p3 = %#X\\n", &p1, p2, *p3);
printf(" &a = %#X, p1 = %#X, *p2 = %#X, **p3 = %#X\\n", &a, p1, *p2, **p3);
return 0;
//以三级指针 p3 为例来分析上面的代码。***p3等价于*(*(*p3))。*p3 得到的是 p2 的值,
也即 p1 的地址;*(*p3) 得到的是 p1 的值,也即 a 的地址;经过三次“取值”操作后,*(*(*p3))
得到的才是 a 的值。
指针数组、指向函数的指针、指向二维数组的指针
指针数组:
指针变量和普通变量一样,也能组成数组,如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
数据类型 * 名字 [数组长度];
这里注意 [ ]的优先级比 * 来得高
int *a [10];
这里说明a是一个数组,包含了10个元素,每个元素的类型为int *。
除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:
#include<stdio.h>
int main(void)
int a = 1;
int b = 2;
int c = 3;
//定义一个指针的数组
int *an[3] = &a,&b,&c ;//由于里边每一个元素都是指针,所以利用取地址符&,指向abc三个变量
//这里定义一个指向指针数组的指针,由于数组已经是指针了,所以要用到二级指针
int **p = an;//由于数组本身就是表示一个地址所以不用取地址符&
printf("%d %d %d\\n", *an[0], *an[1], *an[2]);
printf("%d %d %d\\n", **(p + 0) , **(p + 1), **(p + 2));
return 0;
//arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,
我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。
parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,
它的定义形式应该理解为int *(*parr),括号中的*表示 parr 是一个指针,
括号外面的int *表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int *,
所以在定义 parr 时要加两个 *。
指针数组还可以和字符串数组结合使用:
#include <stdio.h>
int main()
char *str[3] = //定义一个字符串数组 长度为3
"c.biancheng.net",
"C语言中文网",
"C Language"
;
printf("%s\\n%s\\n%s\\n", str[0], str[1], str[2]);//依次输出每个字符串
return 0;
指向函数的指针:
一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。
数据类型 *指针名 (数据类型 参数);
数据为函数返回值类型,指针名称,括号里边为函数参数列表。参数列表中可以同时给出参数的类型和名称,
也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。
注意( )的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType *
用指针来实现对函数的调用:
#include <stdio.h>
//返回两个数中较大的一个
int max(int a, int b)
return a>b ? a : b;
int main(void)
int x, y, maxval;
//定义指向函数指针*pmax
int (*pmax)(int, int) = max; //也可以写作int (*pmax)(int a, int b)
//要注意的是定义必须和函数形式一致
printf("Input two numbers:");
scanf("%d %d", &x, &y);
maxval = (*pmax)(x, y);//将函数调用并指针赋值
printf("Max value: %d\\n", maxval);
return 0;
// maxval 对函数进行了调用。pmax 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。
注意( )的优先级高于*,第一个括号不能省略。
指向二维数组的指针:
(复盘一下二维数组的知识)二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ;
a就是像一个矩阵:
0 1 2 3
4 5 6 7
8 9 10 11
但在内存中,a 的分布是一维线性的,整个数组占用一块连续的内存:
【0】【1】【2】【3】【4】【5】【6】【7】【8】【9】【10】【11】
C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4) = 48 个字节。
C语言把一个二维数组分解成多个一维数组来处理。对于数组 a,它可以分解成三个一维数组,即 a[0]、a[1]、a[2]。每一个一维数组又包含了 4 个元素,例如 a[0] 包含`a[0][0] 、a[0][1]、a[0][2]、a[0][3]。
为了更好的理解指针和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:
int (*p)[4] = a;
//括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],
这正是 a 所包含的每个一维数组的类型。
[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,那么这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
按照上面的定义,我们来看看代码:
#include <stdio.h>
int main(void)
int a[3][4] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 ;
int (*p)[4] = a;
printf ( "%d\\n", sizeof(*(p+1)) );//这里输出是16
return 0;
*(p+1)+1表示第 1 行第 1 个元素的地址:*(p+1)单独使用时表示的是第 1 行数据,放在表达式中会被转换为第 1 行数据的首地址,也就是第 1 行第 0 个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第 0 个元素的指针;就像一维数组的名字,在定义时或者和 sizeof、& 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第 0 个元素的指针。
*(*(p+1)+1)表示第 1 行第 1 个元素的值。很明显,增加一个 * 表示取地址上的数据:**
规律:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
#include<stdio.h>
int main(void)
int a[3][4] = 1,2,3,4,5,6,7,8,9,10,11,12 ;
int i, j;
int(*p)[4] = a;//定义一个指向二维数组的指针p
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
printf("%d ", *(*(p+i)+j));//利用二级指针就可以访问到i行j列的元素
//*(p+i):一维数组
printf("\\n"); //*(*(p+i)+j) 二维数组
return 0;
/*输出:
1 2 3 4
5 6 7 8
9 10 11 12
数组名 a 在表达式中也会被转换为和 p 等价的指针!
指针数组和二维数组指针在定义时非常相似,但是括号的位置不同所表示的意思也就天壤之别:
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
指针数组和二维数组指针有着本质上的区别:
指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
至于多维数组和二维数组没有本质的区别,但是复杂度倒是高了许多。一般不常用。
结束语:
程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。指针就是存放地址的一种变量。
常见的的指针:
1、 指针变量可以进行四则运算。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型与地址有关。
2、给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
3、使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,直接赋值NULL让它变为空指针。
4、数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。
指针的用法暂时就这些,C指针大法这些才是入门!继续加油咯~
作者:Mr_Li_
对啦对啦!另外的话为了帮助大家,轻松,高效学习C语言/C++,我给大家分享我收集的资源,从最零基础开始的教程到C语言项目案例,帮助大家在学习C语言的道路上披荆斩棘!可以来我粉丝群领取哦~
整理分享(多年学习的源码、项目实战视频、项目笔记,基础入门教程)最重要的是你可以在群里面交流提问编程问题哦!(↓↓↓↓↓↓)
零基础学c语言知识总结十:指针做函数参数,指针做函数返回类型(代码片段)
指针做函数参数,指针做函数返回类型有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,但是如果返回值有其它用途(例如返回函数的执行状态量),或者要回传的数据不止一个... 查看详情
零基础学c语言知识总结十:指针及其相关知识(代码片段)
指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅... 查看详情
零基础学c语言内存知识总结:realloc函数和free函数(代码片段)
realloc函数realloc()函数可以重用或扩展以前用malloc()、calloc()及realloc()函数自身分配的内存。函数原型:externvoid*realloc(void*mem_address,unsignedintnewsize);//指针名=(数据类型*)realloc(要改变内存大小的指针名,新... 查看详情
零基础学c语言知识总结十一:动态内存分配!(代码片段)
动态内存分配(动态存储期)在程序执行并使用该变量的时候分配内存空间,使用完毕立即释放.动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存... 查看详情
零基础学c语言知识总结十一:c语言的内存四区(代码片段)
一个正在运行着的C编译程序占用的内存分为代码区、静态数据区、未初始化数据区、堆区和栈区5个部分。C语言中定义4个内存区间是: 代码区, 静态存储区, 栈区,堆区. 其中栈区和堆区是属于动态存储区可执行文件在存储... 查看详情
c语言基础知识:最核心的—指针,知识总结(第一部分)(代码片段)
指针是C语言最重要也是最难理解的部分,它在我们平时的工作中无处不在。有人说学会了指针,C语言也就学会一半。为什么说指针难。因为指针与数组相结合就涉及数组指针与指针数组。指针与结构体结合就涉及结构体... 查看详情
c语言二级指针作为输入(自定义二级指针内存|二级指针排序|通过交换指针指向的内存数据方式进行排序)(代码片段)
...放一维指针|为每个一维指针分配内存|释放二维指针内存)基础上,对二维指针指向的若干一维指针指向的数据进行排序;首先,准备好了循环控制变量,和排序交换时,使用的中间变量;循环控制变量:排序一般需要定义两个变量;//循环... 查看详情
零基础学c语言内存知识总结:memset函数和calloc函数(代码片段)
memset函数memset(翻译:清零)是计算机中C/C++语言初始化函数。作用是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。以前说过,定义变量时一定要进行初始化&... 查看详情
c语言指针知识点总结
参考技术A1.指针的使用和本质分析(1)初学指针使用注意事项 1)指针一定要初始化,否则容易产生野指针(后面会详细说明); 2)指针只保存同类型变量的地址,不同类型指针也不要相互赋值; 3)... 查看详情
零基础学c语言知识总结六:数组,字符与字符串(代码片段)
1、介绍数组一个常量变量就是一个用来存储数值的命名区域。同样,一个数组就是一个用来存储一系列变量值的命名区域,因此,可以使用数组组织常量变量。也就是说,数组是一组有序数据的集合,存储在... 查看详情
零基础学c语言知识总结六:数组,字符与字符串(代码片段)
1、介绍数组一个常量变量就是一个用来存储数值的命名区域。同样,一个数组就是一个用来存储一系列变量值的命名区域,因此,可以使用数组组织常量变量。也就是说,数组是一组有序数据的集合,存储在... 查看详情
c语言二级指针内存模型(指针数组|二维数组|自定义二级指针|将一二模型数据拷贝到三模型中并排序)(代码片段)
文章目录一、指针数组和二维数组数据拷贝到自定义二级指针中1、函数形参设计规则2、三种内存模型对应函数形参指针退化规则二、完整代码示例一、指针数组和二维数组数据拷贝到自定义二级指针中将指针数组和二维数组中... 查看详情
c语言基础知识:最核心的—指针,知识总结(第二部分)(代码片段)
指针是C语言最重要也是最难理解的部分,它在我们平时的工作中无处不在。今天我们继续来看看指针的剩下的知识总结吧!上一批的话可以在主页看到哦~5指针与结构体一个指针,它指向的可以是一个结构体类型... 查看详情
零基础学c语言知识总结三:运算符进制转换输入输出
进制之间的转换其他进制转换为二进制1、八转二:123.6,首先,将每一位数字,一分为三。1=001 2=010 3=011 0.6=110然后合起来:001010011.110=1010011.11(可将整数高位的零省略,小数低位的... 查看详情
c语言二级指针作为输入(自定义二级指针内存|二级指针排序|通过交换指针方式进行排序)(代码片段)
...放一维指针|为每个一维指针分配内存|释放二维指针内存)基础上,对二维指针指向的若干一维指针指向的数据进行排序;首先,准备好了循环控制变量,和排序交换时,使用的中间变量;循环控制变量:排序一般需要定义两个变量;//循环... 查看详情
c语言初阶笔记重点初识指针,详解!!(代码片段)
...系,当然博主也说了这是初阶版的讲解,先打好基础& 查看详情
c语言基础知识:指针和数组的区别是什么?
在C语言教程中我们使用通过数组名通过偏移和指针偏移都可以遍历数组,那么指针和数组到底有什么区别??由于数组中的数据在内存中都是连续存放的,数组名默认就是数组的首地址,也是一个特殊的指针&... 查看详情
零基础学c语言知识总结五:3种必学必会的循环结构(代码片段)
循环结构可以看成是一个条件判断语句和一个向回转向语句的组合。另外,循环结构的三个要素:循环变量、循环体和循环终止条件,循环结构在程序框图中是利用判断框来表示,判断框内写上条件,两个出... 查看详情