指针初阶理解(0基础必看)
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
1.指针是什么
1.1理解内存
1.1.1 内存单元的概念
内存单元:内存被划分为一个个小的内存单元。
一个基本的内存单元的大小是一个字节
如图:
每个内存单元都有自己的编号,内存单元的编号可以快速定位到一个内存的单位。
1.1.2内存单元的编号是如何产生的呢?
32位机器——> 32根地址线——>通电——>高电频/低电频——>1/0
编号是由硬件电路直接产生的
00000000000000000000000000000000
…
11111111111111111111111111111111
总共可以有2的32方次个内存编号
每个内存编号对应一个字节的内存单元 总共可以管理4GB 的内存。
64位机器也是如此,可以有2的64次方个内存编号,管理8GB的内存。
1.2代码理解指针
#include<stdio.h>
int main()
{
int a = 0;
return 0;
}
当我们创建一个int类型的变量a时,内存会给 变量 a分配4个内存单元
调试查看 a的内存,可以得知变量 a的地址是首编号。如图所示:
#include<stdio.h>
int main()
{
int a = 0;
int* pa = &a; //存下a的地址
return 0;
}
int 是指针类型,pa是指针变量,pa可以存放 a的地址,在代码中,pa存的是pa的地址(也就是存的是变量a的第一个内存单元的编号)。*
由于地址是32个0/1组成的,即32个bit位,4个字节的大小,所以指针变量的大小为固定的4个字节(32位机器下)
1.3 总结
指针是用来存放地址的,地址唯一表示一块地址空间。
指针的大小为4个字节(32位机器下) 8个字节(64位机器下)
2.指针和指针类型
2.1指针的解引用
#include<stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0; //将a的内容改为 00 00 00 00
return 0;
}
*是解引用操作符
成功将a 4个字节的内容都改为 0。
2.2指针类型的影响
如果将int* 改为 char* 呢?
#include<stdio.h>
int main()
{
int a = 0x11223344;
char* pa = &a;
*pa = 0;
return 0;
}
经过调试 我们可以得知 我们只改变了一个字节的内容
结论1: 指针类型觉得了在解引用时一次可以访问几个字节(指针权限)
#include<stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("%p\n",pa);
printf("%p\n",pa+1);
printf("%p\n",pc);
printf("%p\n",pc+1);
return 0;
}
可以得到结果:
我们可以通过编译结果 得出 pa+1 跳过了 4个字节,而pc+1却只跳过了1个字节。
结论2:指针类型决定了指针向前走一步或者向后走一步走多大的距离(单位是字节)
实际运用代码:打印数组
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* pa = arr; //数组名是首元素的地址,数组是连续的(已学知识)
int i= 0;
for(i=0;i<10;i++)
{
printf("%d ",*(p+i));
}
return 0;
}
2.3 总结
指针类型觉得了在解引用时一次可以访问几个字节(指针权限)
指针类型决定了指针向前走一步或者向后走一步走多大的距离(单位是字节)
3.野指针
3.1 野指针的概念
:指针指向的位置是不可知的(随机的,不确定的,无明确限制的)
3.2野指针出现的情况
3.2.1 指针未初始化
#include<stdio.h>
int main()
{
int a ;
int* p;
*p = 20;
return 0;
}
产生报错信息:
3.2.2 指针越界访问
#include<stdio.h>
int main()
{
int arr[10] = {0};
int* p = arr;
int i= 0;
for(i=0;i<=10;i++)
{
*p = i;
p++;
}
return 0;
}
产生异常信息:
指针指向的地址超过了 arr的范围。(越界访问造成的野指针问题)。
3.2.3 局部生命周期导致的野指针问题
#include<stdio.h>
int* test()
{
int a = 100; //为a开辟空间
return &a; //返回 a 的地址
}
int main()
{
int* p = test();
printf("%d",*p);
return 0;
}
尽管有的编译器该代码可以打印出 a 的值。但是 a 有自己的生命周期,变量 a 在test 函数结束之后会被销毁。
所以 将局部变量的地址放在指针变量中 ,也会导致野指针出现
3.3 如何避免野指针
3.3.1指针初始化。
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int* p = NULL;
return 0;
return 0;
}
给不需要使用的指针赋值为空指针,在使用空指针时最后用if语句判断一下是否为空,切勿使用空指针。(检测指针的有效性)
3.3.2 小心数组越界。
3.3.3 在一个指针不需要使用时,及时赋值为空指针。
3.3.4 避免返回局部变量的地址
3.3.5指针使用之前检验其有效性
4 指针运算
4.1 指针 ± 整数
指针加减整数跳过多少个字节不仅与整数有关,与指针类型也有关
4.2 指针 - 指针
(+ 没有意义)
前提: 两个指针指向同一块连续的空间
#include<stdio.h>
int main()
{
int a[10] = {0};
printf("%d\n",&a[9] - &a[0]);
printf("%d\n",&a[0] - &a[9]);
return 0;
}
可以得到两个地址之间的元素个数。
写一个球字符串长度的函数:
#include<stdio.h>
int my-strlen(char* s)
{
char* star = s;
while(*s != '\0')
{
s++;
}
return s - star;//得到 数组之间的元素个数
}
int main()
{
char arr[] = "abc";
int len = my-strlen(arr);
printf("%d",len);
return 0;
}
4.3 指针的关系运算
指针可以进行关系运算 ,主要运用在数组相关的运算中
标准规定:
允许指针数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
5 指针和数组
可以通过指针访问数组
#include<stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int* p = arr;
int i = 0;
for(i=0;i<10;i++)
{
printf("%p = %p \n",p+i,&arr[i]);
}
return 0;
}
6.二级指针
直接代码理解:
#include<stdio.h>
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;//二级指针存放一级指针的地址。
int*** pppa = &ppa;//pppa就是三级指针。
return 0;
}
也就是说 二级指针存的是一级指针的地址,多级指针存放的是低一级的指针变量的地址。
7指针数组
初阶简单理解:
存放指针的数组