这篇博客算是指针入门的一些知识点,以后我会出一篇指针进阶的博客,如果想把指针学好的话不要错过。😊
指针初阶
1.指针是什么?
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
简单来说:指针就是地址,口语中说的指针通常指的是指针变量。
开讲之前要先说一下一点内存的知识。
为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
这里的内存其实和我们生活中的楼房是很相似的。
就比如说我们学校的每一栋宿舍楼里面的每一个房间都有对应的门牌号,如果说我想要找到某位同学(在某栋楼的某栋宿舍),我就只需要查出来TA所在的宿舍楼->门牌号即可。
现在我们类比到现在所讲的内存,宿舍楼相当于我们的内存的某一个区,楼里的每一个房间相当于区中的每一个字节(byte)大小的内存单元,那么所有的数据其实就都存放在内存单元中的,只不过有的数据比较大,有的数据比较小(由于数据的类型不同,其所占的空间大小也就不同),所占的内存单元总数不同,这也很好理解,就比方说现实生活中有的人很有钱,可以买好几间房子,而有的人没那么有钱,就只有一间房子可住。那么数据的地址编码就是对应于房间的门牌号。
再给大家讲一下地址是如何生成的:
这就涉及到硬件方面的东西了,每台电脑都对应有32位或64位操作系统,多少位就对应有多少根寻址线,比方说32位操作系统下就有32根寻址线,每一根寻址线都可以通电,电又分为低频和高频,当某一跟线通的是高频时,这根线的二进制表示就是1,同理,通低频时,这根线的二进制表示就是0,这个要留个印象
而且2的32次方对应的就是4G的大小,64次方对应的就是8G的大小对于二进制和十六进制的转换,二进制中连续的4位对应于十六进制中的1位。这样用十六进制表示就会非常的方便。
举个栗子,对于十进制中的8,495
在二进制中就表示为 0010 0001 0010 1111
在十六进制中就表示为 2 1 2 F大家若是不会转换的话可以在网上搜一搜,我就不讲进制转换了。
下面就给大家看一张图来理解一下:
再给大家看看在编译器上的显示
指针变量
&这个符号就是取地址的操作符,就是取出变量的起始地址,我们把这个地址放到一个变量里,那么这个变量就是指针变量
int main()
{
int a = 20;//这是一个整型变量
int* pa = &a;//int*代表整数类型的指针变量pa,pa指向了a的地址。
return 0;
}
指针的大小
指针在32位平台上的大小是4个字节,在64位平台上的大小是8个字节。
直接展示吧:
指针大小要牢记。
2.指针和指针类型
我们都知道,变量有不同的类型,整形,浮点型等。指针同理,也有对应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这些指针的类型都是不同的。
这里就可以知道指针定义的时候就是
类型* 指针变量名字 = 所指向的地址
那指针类型的意义是什么?
2.1指针加减整数
那么就可很形象的表述:指针的类型决定了指针向前或者向后走一步有多大(距离)。
2.2 指针的解引用
那么对于不同类型的指针指向了相同变量的地址时,修改指针变量的值会产生不同的效果。
上例子:
这里打印的结果很奇怪,当然也报错了,我们到内存中细看一下。
对应a的地址处,a本身存放的十六进制是0a 00 00 00,但是p2+1修改了0a后边的00变成了1e,所以就导致了打印a的时候打印了7690。
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
这下应该是懂了吧。
3. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1 野指针成因
- 指针未初始化
- 指针越界访问
这里arr只有10个元素,但是循环了12次。越界访问了。
- 指针指向的空间释放
这里放在动态内存开辟的时候讲解,这里可以简单说一下。我在我的第一篇初识C的那片博客里提到了malloc,free这两个函数,这是我们手动开辟释放空间的,但是也有自动释放的,比如说函数内部创建局部变量的指针,并且我们把这个指针返回了,这里就会导致野指针。
如下:
虽然说成功了但是提示了一个警告:
所以说这个东西不敢乱用,要记好。
3.2 如何规避野指针
- 指针初始化
我们创建变量的时候如果不知道初始化什么值可以初始化为0
同理当我们创建了一个指针变量后不知道该初始化什么值可以先赋值空指针NULL,就像这样:int * p = NULL;
- 小心指针越界
这在上面的数组讲了,注意一下。
- 指针指向空间释放,及时置NULL
在我们free掉了一个指针变量后,要将其置为空指针NULL
- 避免返回局部变量的地址
在上面也说了。
- 指针使用之前检查有效性
assert函数,这个以后会讲。
4.指针运算
指针± 整数
指针-指针
指针的关系运算
4.1 指针±整数
这个上面讲过了。
4.2 指针-指针
结论:两个指针间元素的个数(有正负数之分)。
高地址减去低地址为正,低地址减高地址为负。
4.3 指针的关系运算
实际上就是比大小。
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
vp > &values[0]就是比较两地址大小来确定是否继续循环。
代码简化, 将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上第二种是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
我在前面的博客中也讲到了数组名就代表的是数组首元素的地址。
再来看一个例子:
p+i 其实计算的是数组 arr 下标为i的地址。
6. 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在二级指针中。
先来看代码
对于二级指针的运算有:
*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa
这里就不讲那么细了,等到学进阶的时候我再狠狠的讲。
7. 指针数组
这个也是点到为止。
int* arr[10];
结束。