文章目录
指针是什么?
指针是什么?
指针理解的两个要点:
1.指针是内存中一个最小单元的编号,也就是地址
为了更好的管理和使用内存单元 更快更准的定位到每个单元的内容,我们会把内存划分为一个个小的单元 一个内存单元是一个字节, 把每个内存单元都编号
2.平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
指针变量
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int* pa = &a;//这里我们对变量a,使用&操作符(取地址操作符)取出它的地址
//把a的地址存放在pa变量中,使用这个变量就是使用这个地址,这里的pa就是指针变量
*pa = 20; //*解引用操作 *pa就是通过pa里面的地址,找到a,通过pa来修改a
printf("%d\n", a);
return 0;
}
pa 指针首先是一个变量,它本身也占据一块内存,这块内存里存放的就是a的首地址。
当解引用的时候,就会从这个首地址连续划出4个byte,然后按照int类型的编码方式解释
为什么要有地址呢?
为什么要有地址呢?
可以快速的找到内存单元 这个跟生活中小区的楼房单元门还有室是一样的
假如一天胡图图让你去他家玩,他只告诉你他在番斗大街番斗花园住 ,你肯定是找不到他,如果他又在告诉你番斗花园2号楼1001室,你可以很快的去找到他,这就是地址的意义
内存单元的编号如何产生?
如果当前机器生成32位虚拟地址空间
32位地址 32根地址线中每个存储单元的字长是32位 所以4个字节就可以存放一个地址
如果当前机器生成64位虚拟地址空间
64位地址 64根地址线中每个存储单元的字长是64位 所哟8个字节就可以存放一个地址
32位的CPU的虚拟地址范围为:
00000000000000000000000000000000
…
0111111111111111111111111111111111111
10000000000000000000000000000000
…
1111111111111111111111111111111111111
即:0x00000000 ~ 0xFFFFFFFF,
这里就有2的32次方个地址。
即最大虚拟内存为2^32 Bytes = 4GB。
地址线是用来传输地址信息用的。举个简单的例子:cpu在内存或硬盘里面寻找一个数据时,先通过地址线找到地址,然后再通过数据线将数据取出来。
如果有32根.就可以访问2的32次方的空间,也就是4GB。也就是说,地址线一次确定一个存储单元,地址线上值可能取的所有组合确定了存储单元的个数。所以,存储单元的个数=2^32地址线的条数。我们常说的存储容量就是指存储单元的个数。存储容量=4GB
这里是因为存储单元与字节存在一个一对一的关系,一个存储单元占一个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
指针变量的大小
类型大小都是4字节-如果当前平台是个32位平台,编译出32位的程序,里面不管什么类型的地址都是四个字节,因为地址的大小都是一样的 char,short…类型变量的地址也是个地址
int main()
{
printf("%d\n", sizeof(char*));//4或8
printf("%d\n", sizeof(short*));//4或8
printf("%d\n", sizeof(int*));//4或8
printf("%d\n", sizeof(long*));//4或8
printf("%d\n", sizeof(long long*));//4或8
printf("%d\n", sizeof(float*));//4或8
printf("%d\n", sizeof(double*));//4或8
return 0;
}
在32位的机器上,32个0或者1组成的地址线,用4个字节的空间来存储,所以一个指针变量的大小是4个字节
在64位的机器上,如果有64个地址线,一个指针变量的大小是8个字节,才能存放一个地址
总结:
32位机器或平台上指针大小是4个字节
64位机器或平台上指针大小是8个字节
指针和指针类型
我们都知道,变量有不同的类型,整型,浮点型等。
那指针有没有类型?
有类型.
指针类型有非常重要的意义!
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 类型变量的地址
下面让我们看一个例子说明指针类型的重要性
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
当我们取地址a放到pa中
可以看到a的地址和pa指针变量相同,说明a的地址已经存放到pa中了
当pa是整型指针时,对pa进行解引用 找到了a赋值为0,a被修改了四个字节的数据
那我们来看看pa为 char类型指针
int main()
{
int a = 0x11223344;
char *pa = &a;
*pa = 0;
return 0;
}
可以看到a的地址和ac指针变量相同,说明a的地址已经存放到pa中了
当我们pa是char类型指针时,对pa进行解引用只修改了一个字节的数据
这里只有指针类型不同,说明了指针类型决定了,在被解引用时访问权限有多大 整型指针解引用访问四个字节 字符指针解引用访问一个字节
指针类型其实是一种看待空间的方法 例如 创建一个整型变量,向后访问认为都是四字节整型,所以我们以后使用指针类型时想要以什么方式访问数据,就用什么指针
指针±整数
指针加减数字表示的意义是指指针再数组中位置的移动,对于整个整数部分而言,它代表的是一个元素,对于不同的数据类型,其数组的元素占用的字节是不一样的
指针+1,不是再指针地址的基础之上加一个地址,而是在这个指针地址的基础上加1个元素占用的字节数
例如:
为什么?
就是因为指针类型的差异 整型指针 使用一次访问四个字节 字符指针 使用一次访问一个字节
指针类型决定了我们看待问题的视角
char *p 对p解引用 可以向后访问一个字节 p+1向后访问一个字节
指针的解引用
指针类型发生变化 操作范围也发生变化 指针类型决定了 指针在被解引用的时候访问的权限
整型指针解引用访问四个字节
字符指针解引用访问一个字节
下面我们用个例子来理解一下:
假设你希望访问这40个字节的时候,是以字符为单位访问的
int main()
{
int arr[10] = { 0 };
//假设你希望访问这40个字节的时候,是以字符为单位访问的
char* p = (char*)arr;//char*进行强制类型转换
int i = 0;
for (i = 0; i < 40; i++)
{
*p = 'x';
p++;
}
return 0;
}
/假设你希望访问这40个字节的时候,是以整型为单位访问的
int main()
{
int arr[10] = { 0 };
//假设你希望访问这40个字节的时候,是以整型为单位访问的
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
*p = 0x11223344;
p++;
}
return 0;
}
总结: 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
野指针
概念:野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)
野指针的成因
1.指针未初始化
局部变量指针未初始化,默认为随机值,指向的空间不属于当前程序,但是能找到一块空间
这里的p是野指针 ,指向的空间是未知的,如果不知道给p赋值为什么,可以传入空指针
int main()
{
int* p;
*p = 20;
return 0;
}
2.指针越界访问
当指针指向的范围超出数组arr的范围时,造成越界访问,所以p就是野指针
int main()
{
int arr[5] = { 1,2,3,4,5 };
int i = 0;
int* p = arr;
for (i = 0; i < 10; i++)
{
printf("%d ", *p);
p++;//当指针指向的范围超出数组arr的范围时,造成越界访问,所以p就是野指针
}
return 0;
}
**3.指针指向的空间释放
主函数按顺序进行,进入test函数内部a创建,出函数a销毁,a销毁内存还给操作系统 不属于该程序内容了,如果再使用就造成野指针问题
int* test()
{
int a = 10;
printf("%d\n",a);
return &a;
}
int main()
{
int *p = test();
*p = 20;
printf("%d\n",p);
return 0;
}
这是编译器会提示你,a不属于当前程序,因为已经被释放了
如何规避野指针
1.指针的初始化
2.小心指针越界
3.指针指向空间释放及时放置NULL 让我们不使用指针的时候,及时放置空指针
4.避免返回局部变量的地址
5.指针使用之前检查有效性 指针不为空 才能去使用它
指针运算
指针的关系运算
简单点来说就是指针的比较大小
标准规定: 允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较
但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
允许vp与向后越界的p1(代表values[4]后一个元素的地址)进行比较
但不建议vp与向前越界的p2(代表values[0]前一个元素的地址)进行比较
指针-指针
前提:指针-指针是,两个指针必须指向同一块空间
char ch [5] = 0;
int arr [6] = 0;
&arr[4]-&ch[3];//err 错误的
指针与指针相减,表示两个指针指向的内存位置之间相隔多少个元素(注意是元素,并不是字节数),准确的来说时指针减指针的绝对值得到的是指针和指针之间元素的个数
我们也可以用指针-指针来实现strlen函数
int my_strlen(char* str)
{
char* start = str;
while(*str)//如果str不为0,就++,str为0时停止循环找到'\0';
{
str++;
}
return str-start;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
指针和数组
数组是通过指针访问的
#inclue <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);//数组名
printf("%p\n",&arr[0]);//数组首元素地址
return 0;
}
可以说明数组名和数组首元素的地址一样 那我们来测一测arr和arr[0]的大小是否一样?咦,这里输出40,为什么是整个数组的大小呢?
数组名就是数组首元素的地址
但是有两个例外:
1.sizeof(数组名),数组名不是数组首元素的地址,数组名表示整个数组,计算的是整个数组的大小
2.&arr(取地址数组名),数组名不是数组首元素的地址,数组名表示整个数组,取出的是整个元素的地址
数组是可以通过指针来访问的,我们可以通过p来把arr数组所有元素的找到
可以说明,数组在内存中是连续存放的,当给一个数组的首元素地址,通过指针的加减整数运算,向后一个整型一个整型的进行解引用访问
数组和指针一回事吗?
数组是一块连续的空间,这里面可以放很多元素
指针是存放地址的变量
但是可以通过指针来访问数组
p+i 得到的就是arr[i]的地址
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);//数组名
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);//对首元素取地址
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);//对数组名取地址
printf("%p\n", &arr+1);
return 0;
}
首元素加1跳过一个元素
数组+1跳过一个数组
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
int main()
{
int a = 10;
int* pa = &a;//pa是指针变量(一级指针)
int* * ppa = &pa;//ppa是二级指针
return 0;
}
上面这个例子就是二级指针,我们如何理解二级指针的?
说明pa是指针
int说明pa指向的对象是int类型
说明ppa是指针
int说明pa指向的对象是int类型的如何通过ppa找到a?
*ppa可以找到pa
*pa可以找到a 所以**ppa可以找到a变量
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组
数组有整型数组,字符数组
int arr1[5];//整型数组 存放整型的数组
char ch[5];//字符数组 存放字符的数组 int*
arr2[5]//指针数组 存放指针的数组
那么类比一下,不难想到指针数组的储存方式
举个简单的例子这就是指针数组:
int main()
{
int a = 11;
int b = 12;
int c = 13;
int d = 14;
int e = 15;
int* arr[5] = { &a,&b,&c,&d,&e };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d\n", *(arr[i]));
}
return 0;
}
我们可以使用指针数组简单来实现个二维数组
int main()
{
int arr[] = { 1,2,3,4 };
int arr1[] = { 2,3,4,5 };
int arr2[] = { 3,4,5,6 };
int arr3[] = { 4,5,6,7 };
int* pa[4] = { arr,arr1,arr2,arr3 };
int i = 0;
for (i = 0; i < 4; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", *(pa[i] + j));
}
printf("\n");
}
return 0;
}
个人水平不足 如果代码中有错误,可以多多在评论区指出,一定会及时修改!
谢谢大家看到这里 觉得有收获的话可以三连一下 一起加油!