引言:我将指针分为两个章节进行讲解,由浅入深,循序渐进。
本章重点:
1. 指针是什么?
2. 指针和指针类型
3. 野指针
4. 指针运算
5. 指针和数组
6. 二级指针
7. 指针数组
1. 指针是什么?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。
那么我们可以这样取理解内存单元:
在我的理解里:编号 == 指针 == 地址,它们其实都是一个概念。
但是呢,下面讲到的指针变量,则是装载地址的容器。
总结:指针就是地址,口语上常说的指针通常是指针变量。
到这里,我们来理一下思路:内存的管理主要是由操作系统来决定,编译器会为我们产生出的变量等开辟合适的空间,一个空间中会分出很多个内存单元,一个内存单元会对应一个地址(也就是编号),当我们在创建一个变量的时候,其实也就是在内存中开辟了一块空间,具体开辟多大的空间由我们创建的变量类型决定,那么当创建好了以后,也就会产生一个对应的地址,我们可以通过地址去访问或者修改空间中存储的数据。
案例一:
总结: 指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是:
- 一个小的内存单元到底是多大?(1个字节)
- 如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
那么32根地址线产生的地址就会是:
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0001
…
1111 1111 1111 1111 1111 1111 1111 1111
这里就有2的32次方个地址。
同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,自己计算。
这里我们就明白:
-
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所
以一个指针变量的大小就应该是4个字节。 -
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。
总结:
- 指针是用来存放地址的,地址是唯一标示一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
2. 指针和指针类型
这里我们在讨论一下:指针的类型 我们都知道,变量有不同的类型,整形,浮点型等。那指针有没有类型呢? 准确的说:有的。
当有这样的代码:
int num = 10;
p = #
要将&num(num的地址)保存到p中,我们知道p就是一个指针变量,那它的类型是怎样的呢? 我们给指针变量相应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是:type + *
。 其实: char*
类型的指针是为了存放 char
类型变量的地址。short*
类型的指针是为了存放 short
类型变量的地址。 int*
类型的指针是为了存放 int
类型变量的地址。
注:指针变量并不会因为类型的不同而产生字节的不同。
那指针类型的意义是什么?
2.1 指针±整数
从这个类型我们可以看到,指针类型的不同决定了向前或者向后的一步有多大(字节)。
2.2 指针的解引用
下面我们通过一个简单的例子进行学习
那么我们换个指针变量类型会不会是同样的结果呢?
我们看到通过char*类型进行解引用访问修改空间中的值,但是只能修改一个内存单元空间中的值。
int*
类型解引用了4个字节,char*
类型解引用了1个字节。
总结:指针变量类型的不同决定了对内存空间操作的权限不同,也就是解引用字节个数的不同。
3. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
3.1 野指针的原因
- 指针未初始化。
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
- 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
- 指针指向的空间释放
在后续的章节会有提到,这里只需要知道有这个会造成野指针的存在即可。
3.2 如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 指针使用之前检查有效性
指针初始化
小心指针越界
指针指向空间释放即使置NULL
数组中的等价关系
从这里我们可以看到,通过下标搜索值的方式,
本质也就是通过解引用以及偏移字节的方式去访问值
。
既然*(arr + i) == arr[i],那*(i + arr) 是否等价于arr[i]呢?你可能会说是等价的,但是下面这种写法你认为是否可行呢?
i[arr] == arr[i]
,下面我们就来验证一下。
可以发现这样奇怪的形式也是可行的。
4. 指针运算
4.1 指针±整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
4.2 指针-指针
画图详解
总结:指针 - 指针得到的数值的绝对值是指针和指针之间元素的个数。
指针和指针运算的前提是必须都指向同一块空间,不然就没有运算的意义。
4.3 指针的关系运算
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
代码简化, 这将代码修改如下:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许
与指向第一个元素之前的那个内存位置的指针进行比较。
5. 指针和数组
先做总结:
我遇到很多的理解都是将指针和数组混为一谈的,认为数组名就是地址,那么指针==地址, 所以指针 == 数组,这种理解其实不对的。
我们常说的指针其实是一个指针变量,装载了一个地址,地址也就是指向了一块空间,里面有我们存放的数据;而我们常说的数组,其实就是一块装有相同类型的多个元素数据的空间。
它们之间的联系其实就在于,数组名是地址(我们知道数组是连续存放的空间),而我们可以通过地址去遍历出这块空间的所有数据。
所以,指针就是指针,数组就是数组,它们之间有关联性,但还是不能混为一谈。
数组名是什么?我们看一个例子:
可见数组名和数组首元素的地址是一样的。
结论: 数组名表示的是数组首元素的地址。
但是有两个例外:
- sizeof(数组名)
- &数组名
这里的数组名代表的是整个数组。
那么这样写代码是可行的:
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
//p存放的是数组首元素的地址
//我们知道首元素,可以利用首元素往下遍历数组中的其他元素
int *p = arr;
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个就成为可能。
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p+i);
}
return 0;
}
输出结果:
6. 二级指针
二级指针的基本理解:
下面我们可以尝试练习一下二级指针的案例:
再次进行总结:
pparr存储了parr首元素的地址,pparr + 1从指向parr首元素的地址变成了指向parr数组中第二个元素的地址,此时仅仅还只是指向第二个元素的地址
,当我们进行第一次解引用的时候,从指向第二个元素的地址变成了指向第二个元素的首元素的地址,虽然看起来没有改变,但内在的意义却变了,当我们进行第二次解引用的时候,也就是指向第二个元素的首元素的地址进行解引用,最后的结果也就是拿到了这块地址后面空间的值。
7. 指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
数组我们已经知道整形数组,字符数组。
int arr1[5];
char arr2[6];
那指针数组是怎样的?
int* arr3[5];//是什么?
arr3是一个数组,有五个元素,每个元素是一个整形指针。
做一个简单的练习题:
本章的内容到此就结束了,觉得对你有所帮助的话,不妨留下一个赞吧~