我们常说c/c++的独特之处就在于它的指针,同时这部分的学习也是较为困难的,本篇文章就向大家分享一些关于指针的理解方式:
指针是什么?
我们可以将指针看作一个“指路牌”,其上明确地写着指向某一“地点”(即一个内存中的元素地址)的路,我们想要找到这个“地点”(即访问内存操作)可以直接通过这个“指路牌”快速找到。
那么将上述的话用稍微专业一点的术语表示则为:指针用于存放地址,地址唯一标识一块内存空间。
指针存储的是地址,计算机对信息的存储根本上是对一串正负电的排列(因为计算机通的就是交流电),这里我们以x32(32位)机器为例:
#include<stdio.h>
int main()
{
int a = 0;//a的存储如下:
//00000000 00000000 00000000 00000000
return 0;
}
32位机器中即保存32个电信号(用0和1表示),一个电信号的存储空间我们称之为1位(bit),x32即有32位,每8位称为一个字节。
计算机通过存储正负电信号,在内存中记下来,通过这种特定的1/0序列存储一个特定的信息(int型、char型等),而指针的作用就是将这种序列的“位置”存下来,所以也有一种说法是:
·指针就是地址,地址就是指针。
这种说法尽管有点小瑕疵,但是也可以作为一种初期理解方式。
指针的大小
指针本身也是一种变量,只不过指针的内容存的是其他变量的地址,所以指针本身大小一般也为32位,即4个字节,x64机器中就是64位,即8个字节,所以:
·指针的大小为4个字节或者8个字节
3.指针的种类
(1)字符/整形型指针
#include<stdio.h>
int main()
{
int a = 0;
//创建一个整形数据a
char arr[] = {'a','b','c'};
//创建一个字符数组arr
char *parr = &a[0];
//创建一个指针变量将arr中下标为0的元素地址存起来
int *pa = &a;
//创建一个指针变量将a的地址存起来
return 0;
}
如上就是一个创建简单的字符型/整型指针,存储了相应的元素地址。
可以看到,对一个指针变量的创建与创建一个基本的元素类型是极其相似的,只不过多了一个符号
“ * ”,我们知道,将一个类型的名称去掉,剩下的就是它的数据类型,所以我们将变量名parr去掉,得到的即为:”char *“,所以char *就是字符型指针;将变量名a去掉,得到的int *就是整形指针。
同理,创建其他简单指针都是如此,在原有的类型后加上“ * ”即可。
(2)数组指针/指针数组
1、数组指针:
顾名思义,数组指针本质上还是一个指针,但是指向的是一个数组,也就是说,这个指针存放的是一个数组的地址,我们用如下例程来分析:
#include<stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};//创建一个整型数组arr
int (*parr)[5] = &arr;
//创建parr指针指向数组arr
return 0;
}
首先那我们得到了一个数组指针int (*parr)[5],那么这个指针是怎么得来的呢?
上文提到过,创建一个简单的指针时,在原有的类型加上“ * ”即可,且将一个类型的名称去掉,剩下的就是它的数据类型,那么arr数组的数据类型即为:
int [5]
那么创建该数据类型的指针加上“ * ”即可:
int(*) [5](加上括号是因为”[ ]“的结合优先级更高,但是我们这里需要名称与”*“结合)
之后再将指针的名字parr写进去即可:
int (*parr)[5]
这样就正确创建了一个“数组指针”,可以存放数组arr的地址。
2、指针数组:
指针数组,根据名称可以知道,首先是一个“数组”,其次是一个存放“指针”的数组,所以指针数组的数据类型是数组,那么就有如下代码:
#include<stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int b[5] = {1,2,3,4,5};
int c[5] = {1,2,3,4,5};//创建整型数组
int *p[] = {a,b,c};
//创建指针数组存放a、b、c三个数组的地址
return 0;
}
因为指针数组的本质还是数组,所以这里我们以数组的视角来分析:
创建整型数组a的方式是int a[5],去掉名称a,数组的类型就是:
int [5]
含义为:数组内有五个元素,每个元素类型都是int,所以称为整形数组。
那么一个指针数组的含义就是:数组内的元素每个都是指针类型,那么我们就可以写出:
int *[5]
即为一个数组有五个元素,每个元素类型都是int *类型,那么再加上名称p,即为:
int *p[5]
(上文说过,”[ ]“的结合优先级更高,所以这里的p会跟后方的[]直接结合,形成一个数组 p[],一个整型数组)
这样就创建好了一个整形指针数组。
(3)函数指针
最后就是函数指针了,函数指针就是指向函数的指针,存放的是函数的地址,一般在我们编写“回调函数”等的时候会遇到(回调函数在文末介绍)。我们根据如下例程来分析:
#include<stdio.h>
int Add(int x, int y)
{
int nt z = x + y;
return z;
}//书写一个简单的相加函数
int main()
{
int (*p)(int , int) = Add;
//存放函数Add的地址
return 0;
}
如上我们可以看到“int (*p)(int , int)”就是一个函数指针,接下来刨析这样的指针是怎样书写出来的:
int Add(int x, int y)是Add函数,我们去函数名称Add,形参变量名称x、y,就能得到它的数据类型:
int(int , int );
接下来要写一个这样类型的指针,与上文一样,在类型中加上“ * ”即可:
int (*)(int , int );
这样我们就写出了Add函数的指针类型,之后再加上指针名称p:
int (*p)(int , int );
这样,我们就写出了一个正确的函数指针。
以此类推,其他函数指针也只需声明清楚所要指向的函数类型即可。
(4)多级指针
一般情况下只会经常遇见二级指针,更高级的指针少见,所以我们以二级指针展开。
前边说过,指针也是一个变量,所以指针在内存中也有它自己的地址,而指针本身又是存储地址的,所以理论上来讲,我们可以用一个指针来存放另一个指针的地址,像这样的指针就称为二级指针。
#include<stdio.h>
int main()
{
int a = 0;
int *pa = &a;//一级指针存储a的地址
int **f = &pa;//二级指针,存储pa的地址
return 0;
}
如上,有几级指针就可以用几次“ * ”来声明,三级指针就是
int ***fp = &f;
这就是高级指针的相关概念。
4.汇总
综上,在编写指针相关概念的时候,我们要秉持以下原则:
指针是一种数据类型,用于存放地址。
无论指针指向何种数据类型,一定要声明清楚指针所指向的类型。
创建指针时一定要有指向的具体类型(如暂定,则用NULL置空),野指针是非常危险的情况。
小tip:
回调函数相关概念:
将一个函数A传给另一个函数B,在B中用A的地址去调用A,就称A函数为回调函数。
~~~~~~点赞加关注,学c不迷路~~~~~~·