指针是什么
指针
指针是内存中一个最小单元的编号,也就是地址
平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量
指针变量
我们可以通过&(取地址操作符)取出变量的内存起始地址,把地址存放到一个变量中,这个变量就是指针变量
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int* p = &a;//使用&操作符取出变量a的地址,a变量占四个字节,
//将这四个字节的第一个字节的地址存放在p变量中,p就是一个指针变量。
return 0;
}
总结:
指针变量,是用来存放地址的变量。(存放在指针中的值都被当成地址处理)
思考一:
最小单元到底是多大?
答案是1个字节,因为数据类型最小的char就占一个字节。如果最小单元是一个比特位的话,那么我们来描述一个变量的地址就太过复杂,如果最小单元大于一个字节,那么描述变量地址就太过麻烦。
思考二:
如何进行编址的?
对于32位的机器,假设有32根地址线,每根地址线在寻址的时候产生高电平(高电压)和低电平(低电压)就是(1或0)。那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
……
11111111 11111111 11111111 11111111
有2的32次方个地址。
每个地址标识一个字节,那么就可以给(2^32Byte == 2^32/1024KB == 2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB
)4G的内存进行编址。
同样的方法,64位机器可以给2^64Byte ==16G的内存进行编址。
这里我们就明白:
- 在32位的机器上,地址是32个0或者1组成的二进制序列,所以地址就得用4个字节的空间来存储,所以一个指针变量的打小就应该是4个字节。
- 如果在64位机器上,那么地址就是有64个0或者1组成的二进制序列,那么指针变量的大小为8个字节。
总结:
- 指针是用来存放地址的,地址是唯一标识一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节
指针和指针类型
我们知道变量都是有类型的,所以指针变量也是有类型的。
char* p1 = NULL;
short* p2 = NULL;
int* p3 = NULL;
long * p4 = NULL;
long long* p5 = NULL;
float* p6 = NULL;
double* p7 = NULL;
可以看到,指针的类型是:type + *
那么可以猜到:
char*
类型的指针是为了存放char
类型变量的地址;
short*
类型的指针是为了存放short
类型变量的地址;
int*
类型的指针是为了存放int
类型变量的地址;
那么指针类型的意义是什么呢?
指针 + - 正数
可以看到指针的类型不同,指针走下一步的距离就有所不同,其实也很好理解。
char*类型的指针以为这块内存存储的是char类型的数据,那么访问下一个数据就应该是走一个字节的距离;
int*类型的指针以为这块内存存储的是int类型的数据,那么访问下一个数据就应该是走四个字节的距离;
**总结:**指针的类型决定了指针向前或者向后走一步有多大(距离)。
指针的解引用
可以看到,指针的类型不同,解引用访问的内存大小就不同,其实也好理解。
char*类型的指针,以为这块内存放的是char类型的数据,想要访问该数据,那自然就该访问一个字节空间;
int*类型的指针,以为这块内存放的是int类型的数据,想要访问该数据,那自然就该访问四个字节空间。
总结:
指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)。
这也就是指针类型存在的意义!!!
野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
野指针成因
- 指针未初始化
#include <stdio.h>
int main()
{
int* p;//局部变量未初始化,默认为随机值
*p = 20;//而对一个随机指向的内存进行解引用赋值,是错误的。
return 0;
}
- 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
for(int i = 0; i <= 10; ++i)
{
//当循环第十一次时,p的指向就超出了数组的范围,这同样是错误的。
*(p++) = i;
}
return 0;
}
- 指针指向的空间释放
当指针指向的空间释放之后,指针指向的空间就不属于自己了,属于操作系统了,这同样是野指针。
如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性
指针运算
指针 + - 整数
指针的+ - n,意味着指针指向原地址向前或向后第n个内存地址
指针 - 指针
指针 - 指针,结果意味着这两个指针之间的内存空间数量(单位是指针指向变量的类型)。(同类型指针)
指针的关系运算
#include <stdio.h>
int main()
{
int arr[5] = { 0 };
//代码一:
for (int* p = &arr[5]; p > &arr[0];)
{
*--p = 0;
}
//代码二:
for (int* p = &arr[4]; p >= &arr[0]; --p)
{
*p = 0;
}
return 0;
}
这两个代码的区别就是最终p指向的地址不同,代码一中p最终指向了数组的第一个元素处,而代码二中p最终指向了数组第一个元素的前一个位置处,这块位置是未定义的。
代码二在绝大部分的编译器上是可以完成任务的,但是应该避免这样写,因为标准未定义。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
可见数组名和数组首元素的地址是一样的。
结论:数组名表示的是数组首元素的地址。(除了两种情况:一、sizeof(地址),二、&数组名)
那么就可以这样写:
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr;//p存放的是数组首元素的地址
还可以用指针来访问数组元素
二级指针
指针变量也是变量,那么就也会有地址,而存放指针变量的地址的变量就叫做二级指针变量。
对于二级指针的运算有:
-
*ppa
,通过对ppa中的地址进行解引用,这样找到的是pa
,*ppa
,其实访问的就是pa
.int a = 10; *ppa = &a;//等价于 pa = &b;
-
**ppa
,先通过*ppa
找到pa
,然后再对pa
进行解引用操作:*pa
,那找到的就是a
.**ppa = 30; //等价于*pa = 30; //等价于a = 30;
指针数组
指针数组是数组。是存放指针的数组。
int arr1[5];//存放5个int类型的整型
char arr2[5];//存放5个char类型的字符
那么
int* arr3[5];//存放5个int*类型的指针
char* arr4[5];//存放5个char*类型的指针