1.指针是什么?
官方定义:在计算机科学中,指针(Pointer)是编程语言的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元,因此,将地址形象化的称为“指针”。意思是通过它可以找到以它为地址的内存单元。
简而言之,指针就是变量,用来存放地址的变量(存放在指针中的值都被当做地址处理)
那么一个小的单元到底是多大呢?(一个字节)
如何编址?
经过仔细的计算和权衡发现一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线,那么假设每一根地址线在寻址的是产生一个电信号正电/负电(1/0)
那么32根地址线产生的地址就是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000
…
11111111 11111111 11111111 11111111
这里就有2的32次方个地址,每个地址识别一个字节,那就可以算出:
2^32 / 1024 / 1024 /1024 = 4GB,就有4gb的空间进行编制
所以就有:
在32位机器上,地址是32个0或1组成的二进制序列,那地址就得用4个字节的空间来储存,所以一个指针变量的大小就应该是4个字节
那如果在64位的机器上,如果有64根地址线,那一个指针变量的大小就是8个字节,才能存放一个地址
总结:
指针是用来存放地址的,地址是唯一标示一块地址空间的
指针在32位平台上是4个字节,在64位平台上是8个字节
2.指针和指针的类型
1.指针类型决定了指针解引用的权限有多大(能操作几个字节),比如int类型的指针可以访问4个字节,char类型的指针可以访问1个字节,double类型的指针可以访问8个字节!
2.指针类型决定了,指针走一步,能走多远(步长)比如:int类型的指针 *p+1会跳过4个字节,char类型的指针 *pc+1只会跳过一个字节。
3.野指针
概念:野指针就是指针指向的位置都是不可知的(随机的,不正确的,没有明确限制的)
是什么导致了野指针的出现:
1.指针未初始化
举例:
int main()
{
//这里p就是野指针
int* p;// p是一个局部变量,局部变量不初始化的话,默认是随机值
*p = 20;// 非法访问内存了
return 0;
}
2.指针越界访问
举例:
int mian()
{
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)//循环了11次,但是arr数组只有10个元素
{
*p = i;
p++;//当指针指向的范围超过了数组arr的范围时,p就是野指针。
}
return 0;
}
3.指针指向的空间释放
指针创建的时候分配了内存空间,后来释放了空间,还给了系统,但是指针依然记住了这个地址,但是再次访问这个地址是不行的。因为已经还给操作系统了
举个例子:我们到宾馆开了一间房,开了一个晚上,第二天退房之后,就不能继续住那个房间了,虽然住过,但现在已经还给人家了!
如何规避野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放及时放置NULL
4.指针使用之前检查有效性
4.指针运算
指针可以进行以下运算
指针 + 整数
指针 - 整数
举例:
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pb = &arr[9];
int i = 0;
for (i = 0; i < 10; i++)//打印数组每个元素
{
printf("%d ", *p);
*p = *p + 1;
}
printf("\n");
for (i = 0; i < 10; i++)//倒序打印
{
printf("%d ", *pb);
*pb = *pb - 1;
}
return 0;
}
指针 - 指针
举例:
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%d", &arr[4] - &arr[0]);// 4
return 0;
}
指针 - 指针得到的是两个指针之间的元素个数。
指针和指针相减的前提是:两个指针指向同一块空间。
还可以利用指针减指针得到两个指针之间的元素个数这一特性来实现strlen库函数,举例:
#include<stdio.h>
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
int main()
{
int len = my_strlen("abcdefg");
printf("%d", len);
return 0;
}
指针的关系运算
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p;
for (p = &arr[5]; p > arr; p--);
{
*p = 0;
}
return 0;
}
实际上在绝大部分的编译器上可以顺利完成任务的,然而我们还是一个避免这样写,因为标准并不代表它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向那个第一个元素之前的那个内存位置的指针进行比较
5.指针和数组
指针和数组其实上面就已经用到了很多
int arr[10] = {0};
int* p = arr;
数组名是首元素的地址
p++;就可以很好的访问数组里面的每一个元素。
6.二级指针
指针变量也是变量,是变量就有地址,那么指针变量的地址存放在哪里呢?所以就有了二级指针
举例:
int mian()
{
int a = 0;
int* pa = &a;// pa是指针变量,一级指针
// ppa就是二级指针变量
int** ppa = &pa;// pa也是变量,&pa取出内存中pa的地址
return 0;
}
分析:pa指针变量中放了a的地址,但是pa自己也有地址,int* 是因为pa是整形指针变量,所以ppa也要是整形的指针,而第二个*告诉我们ppa也是一个指针,所以 *ppa里面放的是pa的地址,注意不是a的地址,要想通过ppa访问到a的地址,**ppa就可以实现
7.指针数组
顾名思义,指针数组就是用来存放指针的 数组
int main()
{
int arr[10];// 整形数组-存放整数的数组就是整形数组
char ch[5]; // 字符数组-存放字符的数组就是字符数组
// 指针数组 - 存放指针的数组
int* parr[10];//整形指针数组
int* pch[5]; //字符指针数组
return 0;
}